直接步入正题:
前言
工作中可能会遇到这样一个问题,我明明把数据拷贝了一份,但改变拷贝后的数据居然同时会改变原始数据中的值。那就来看看为什么吧,希望这篇文章可以给你解惑。
什么是浅拷贝
「只拷贝了数据对象的第一层,深层次的数据值与原始数据会互相影响(拷贝后的数据与原始数据还存有关联)」
常见浅拷贝的方式:Object.assign()、扩展运算符
const obj1 = { name: 'dog', info: { age: 3 } }
const obj2 = Object.assign({}, obj1)
// 或者
const obj2 = { ...obj1 }
obj2.name = 'cat'
obj2.info.age = 4
console.log(obj1) // { name: 'dog', info: { age: 4 } }
console.log(obj2) // { name: 'cat', info: { age: 4 } }
当拷贝后的对象obj2数据改变的时候会影响原始数据obj1,因为info对象拷贝的是源对象指针。
不清楚指针的可以看前面的这篇文章
JS基础面试高频(一):数据类型
什么是深拷贝
「不管数据对象有多少层,改变拷贝后的值都不会影响原始数据的值。(拷贝后的数据与原始数据毫无关系)」
常见深拷贝的方式:JSON.parse()和JSON.stringify()配合使用,这种方式有一个弊端,就是无法正确处理函数 正则 undefined Symbol(原因是JSON.stringify时,会先进行一个JSON格式化,获得安全的JSON值,因此如果是非安全的JSON值,就会被丢弃掉。其中undefined、function、symbol这三种类型的值就是非安全的,并且set、map这种数据格式的对象,也并没有被正确处理,而是处理成了一个空对象)
const obj1 = { name: 'dog', info: { age: 3 }, fn: function () {} }
const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = 'cat'
obj2.info.age = 4
console.log(obj1) // { name: 'dog', info: { age: 3 }, fn: function(){} }
console.log(obj2) // { name: 'cat', info: { age: 4 } }
我们也可以手写一个深拷贝的方法(简易基础版)
function deepClone(source) {
// null数据需要特殊处理
if (source === null) {
return source
}
// 校验要拷贝的数据是对象还是数组
const target = Array.isArray(source) ? [] : {}
for (const k in source) {
const val = source[k]
const valueType = typeof val
// 校验拷贝的数据类型
if (valueType === 'function') {
target[k] = new Function(`return ${val.toString()}`)()
} else if (valueType === 'object') {
target[k] = deepClone(val)
} else {
target[k] = val
}
}
return target
}
const obj1 = { name: 'dog', info: { age: 3 }, fn: function () {} }
const obj2 = deepClone(obj1)
obj2.name = 'cat'
obj2.info.age = 4
console.log(obj1) // { name: 'dog', info: { age: 3 }, fn: function(){} }
console.log(obj2) // { name: 'cat', info: { age: 4 }, fn: function(){} }
当然还可以使用第三方库Lodash中的_.cloneDeep(value),强烈推荐这个库,功能非常强大!
实现方法
提到深拷贝和浅拷贝,就要涉及到两个知识点,一个是基本是据类型和引用数据类型,一个是堆内存和栈内存
浅拷贝
浅拷贝实现的方式
let obj2 = obj1;
let obj2 = Object.assign({}, obj1);
let arr2 = [...arr1];或者let obj2 = {..obj1};
let arr2 = [].concat.(arr1);
let arr2 = arr1.slice();
let arr2 = Array.from(arr1);
数组还有其他的方法map(),reduce(),filter()都能实现浅拷贝
深拷贝
深拷贝实现的方法
let obj2 = JSON.parse(JSON.stringify(obj1))
原理:基本类型拷贝是直接在栈内存新开空间,直接复制一份名-值,两者互不影响。而引用数据类型,比如对象,变量名在栈内存,值在堆内存,拷贝只是拷贝了堆内存提供的指向值的地址,而JSON.stringify()巧就巧在能将一个对象转换成字符串,也就是基本类型,那这里的原理就是先利用JSON.stringify()将对象转变成基本数据类型,然后使用了基本类型的拷贝方式,再利用JSON.parse()将这个字符串还原成一个对象,达到了深拷贝的目的。
递归的方法
// 深拷贝
function deepClone(data, hash = new WeakMap()) {
if (typeof data !== "object" || data === null) {
return data;
}
if (hash.has(data)) {
return hash.get(data);
}
let newData;
if (Array.isArray(data)) {
newData = [];
} else if (data instanceof Date) {
return new Date(data);
} else if (data instanceof RegExp) {
const clonedRegExp = new RegExp(data.source, data.flags);
if (data.global || data.sticky) {
clonedRegExp.lastIndex = data.lastIndex;
}
return clonedRegExp;
} else if (data instanceof Set) {
newData = new Set();
data.forEach((item) => newData.add(deepClone(item, hash)));
} else if (data instanceof Map) {
newData = new Map();
data.forEach((value, key) => newData.set(key, deepClone(value, hash)));
} else if (data instanceof WeakMap || data instanceof WeakSet) {
return data; // 无法克隆 WeakMap/WeakSet,直接返回原对象
} else if (typeof data === "function") {
const clonedFunc = function (...args) {
return data.apply(this, args);
};
Object.defineProperties(clonedFunc, Object.getOwnPropertyDescriptors(data));
return clonedFunc;
} else {
const descriptors = Object.getOwnPropertyDescriptors(data);
const proto = Object.getPrototypeOf(data);
newData = proto
? Object.create(proto, descriptors)
: Object.create(Object.prototype);
}
hash.set(data, newData);
if (Array.isArray(data)) {
data.forEach((item, index) => {
newData[index] = deepClone(item, hash);
});
} else {
Reflect.ownKeys(data).forEach((key) => {
const descriptor = Object.getOwnPropertyDescriptor(data, key);
if (descriptor.get || descriptor.set) {
Object.defineProperty(newData, key, descriptor);
} else {
newData[key] = deepClone(data[key], hash);
}
});
}
// 处理 Map/Set 里的 Symbol 属性
if (data instanceof Map || data instanceof Set) {
Reflect.ownKeys(data).forEach((symKey) => {
if (typeof symKey === "symbol") {
newData[symKey] = deepClone(data[symKey], hash);
}
});
}
return newData;
}