深浅拷贝是因为计算机对js不同数据类型存储方式不同才引申出来的
对于基本数据类型,存储在栈中,在复制基本数据类型的时候,会在栈中开辟新空间,将值复制给新的变量(对于基本数据类型来说都是深拷贝),修改旧的变量不会影响新的变量。
而对于引用数据类型的值是在堆中存储,栈中存储的是对堆中实际数据的引用(指针),在复制引用数据类型时,会将对堆中的引用复制给新的变量(也就是浅拷贝),因此,新的变量和旧的变量保存的都是对同一个堆的引用,所以在修改堆中的数据时,新的变量也会跟着修改,因此引申出深拷贝的概念。
在日常使用中比较常见的深拷贝的方式:
1.JSON.parse(JSON.stringify(obj))
这种使用方式对于简单的数据类型(数组,对象)来说完全够用,但是需要注意的是,function ,undefined,等,会导致序列化时丢失数据,而Date类型的则会转成字符串,NaN则会转成null,(Symbol和正则也会有问题)
如
let obj = {
a: 1,
b: 2,
c: [1, 2],
d: function () {
console.log("test");
},
e: undefined,
f: null,
g:new Date,
h:NaN
};
console.log(JSON.stringify(obj))
// {"a":1,"b":2,"c":[1,2],"f":null,"g":"2024-08-26T14:59:41.359Z","h":null}
除此之外循环引用也会导致序列化失败报错
let obj = {};
obj.self = obj;
let copy = JSON.parse(JSON.stringify(obj)); // 会抛出错误 "TypeError: Converting circular structure to JSON"
2.使用专门的库(如 Lodash 的 _.cloneDeep)
这个不多赘述,自己写深拷贝要考虑的情况太多,递归(考虑到二维数组等的情况),数据类型的判断等,重复造轮子的事就不必干了
再说一个
Object.assign()
Object.assign 进行的是浅拷贝,而不是深拷贝
有个列子可以看下
let obj1 = { a: { b: 1 } };
let obj2 = Object.assign({}, obj1);
obj1.a = {c:1};
console.log(obj1, obj2);
此时打印出
{ a: { c:1 } } { a:{ b:1 } }
有的人认为这不是也没改变吗,这不就是深拷贝,其实不然,正是因为是浅拷贝(obj1.a和obj2.a是同一个堆得引用)才会导致这种情况
因为obj1.a = {c:1} 这一步 会将新的堆的引用赋值给obj1.a 而obj2.a因为和之前obj1.a引用的是一个地址,所以不会改变
再看下面的例子
let obj1 = { a: { b: 1 } };
let obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b; // 2
此时的obj2.a.b 的值就是2 ,恰巧说明了Object.assign 进行的是浅拷贝,而不是深拷贝