关于对象的深拷贝和浅拷贝,通俗点讲,对象B复制对象A,当改变B的时候,A跟着变,那么就是浅拷贝,如果A不变,两者相互不影响,那么就是深拷贝。要理解对象的深拷贝和浅拷贝,得先理解 js 中值类型和引用类型在内存中的存储方式,可以看看这篇文章。
值类型(Stack):
包括 Number, String, Boolean, Null, Undefined, Symbol(ES6),拷贝操作均属于深拷贝
,因为它们操作的是实际值。
引用类型(Heap):
包括 Array, Object, Function 等对象,拷贝操作可以是浅拷贝,也可以是深拷贝。接下来通过示例来揭开深拷贝和浅拷贝的神秘面纱。
值类型拷贝都是深拷贝(number, string, boolean, null, undefined, symbol)。
let a = "hello";
let b = a;
b = "world";
console.log(a);
// hello
引用类型 array 浅拷贝
let arrA = [1, 2, 3, 4, 5];
let arrB = arrA;
arrB.push(6);
console.log(arrA);
// [1, 2, 3, 4, 5, 6] A 同时发生了变化
引用类型 object 浅拷贝
let objA = { name: "zhangsan", age: 30, course: { chinese: 100 } };
let objB = objA;
objB.name = "lisi";
objB.course.chinese = 89;
console.log(JSON.stringify(objA));
// {"name":"lisi","age":30,"course":{"chinese":89}}
对于单个层级的对象,使用 ES6 语法块 { ... x}
或者 Object.assign()
即可实现深拷贝。对于嵌套对象,使用这两个方法就“歇菜了”。
let objA = { name: "zhangsan", age: 30 };
let objB = {...objA};
// let objB = Object.assign({},objA);
objB.name = "lisi";
console.log(objA);
// {name: "zhangsan", age: 30}
浅拷贝例子
let objA = { name: "zhangsan", age: 30, course: { chinese: 100 } };
let objB = {...objA}; // 不好用了吧
objB.name = "lisi";
objB.course.chinese = 89;
// {"name":"zhangsan","age":30,"course":{"chinese":89}}
嵌套对象,比较“暴力”的深拷贝方法是使用 JSON.parse(JSON.stringify(objA))
。
let objA = { name: "zhangsan", age: 30, course: { chinese: 100 } };
let objB = JSON.parse(JSON.stringify(objA));
objB.name = "lisi";
objB.course.chinese = 89;
// {"name":"zhangsan","age":30,"course":{"chinese":100}}
然而这种方式显得不够优雅,我们来尝试手写一个 工具方法 DeepClone 实现深拷贝吧。
直接上代码,利用递归
的方式实现对象的层层遍历。下面的代码并不完善,例如没有考虑对 date,file,map, set等对象的拷贝。经典库 loadash 提供了 cloneDeep 来实现深拷贝,阅读其源码,发现对10多种常见的对象做了处理,已经较为完善。
function deepClone(targetObj) {
let type = Object.prototype.toString.call(targetObj);
let newObj;
if (type === "[object Object]") {
newObj = {};
} else if (type === "[object Array]") {
newObj = [];
} else {
return targetObj;
}
for(key in targetObj) {
let value = targetObj[key];
newObj[key] = deepClone(value);
}
return newObj;
}