浅拷贝只复制指向某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,它们是完全隔离,互不影响的,对一个对象的修改并不会影响另一个对象。
区别:浅拷贝只复制对象的第一层属性,深拷贝可以对对象的属性进行递归复制,直至属性值为基本类型;
那么如何实现一个深拷贝呢?
下面这段代码通过递归复制,实现了一个初步的深拷贝函数,但是并不完善,具体见代码。
// 1-简单版
// 不足:(1)拷贝后的复杂数据类型只有对象和数组两种
// (2)对于引用自身的属性直接跳过
// (3)无法拷贝特殊类型 Date 、RegExp
function deepCopy(obj1) {
const obj2 = Array.isArray(obj1) ? [] : {};
if (obj1 && typeof obj1 === "object") {
for (let i in obj1) {
let prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj1
if (prop === obj1) {
continue;
}
// 仅拷贝对象自身属性
if (obj1.hasOwnProperty(i)) {
// 如果子属性为引用数据类型,递归复制
if (prop && typeof prop === "object") {
obj2[i] = deepCopy(prop);
} else {
// 如果是基本数据类型,只是简单的复制
obj2[i] = prop;
}
}
}
}
return obj2;
}
改进后的深拷贝函数如下:
//2-升级版
// 可处理特殊类型 Date 、RegExp
// 可处理存在循环引用的对象,如 obj.a=obj
// 不足:无法深拷贝对象的函数属性(可用eval处理箭头函数,但处理不了一般函数,以下代码没实现)
// 实际中,可以用 lodash 库提供的 cloneDeep 来实现深拷贝,不过它也未实现对函数深拷贝,
// An empty object is returned for uncloneable values such as error objects, functions, DOM nodes, and WeakMaps.
// https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
function deepCopy2(obj,weakmap=new WeakMap()) {
if(weakmap.has(obj)){ return weakmap.get(obj)}
if(obj===null){ return null;}
if(obj instanceof RegExp){
return new RegExp(obj);
}
if(obj instanceof Date){
return new Date(obj);
}
//基本数据类型或者函数,直接返回
//这样处理函数就不是深拷贝了
const type=typeof obj;
if(type !=='object'){
return obj;
}
/**
* 如果obj是数组,那么 obj.constructor 是 [Function: Array]
* 如果obj是对象,那么 obj.constructor 是 [Function: Object]
*/
let cons=obj.constructor;
let res=new obj.constructor();
// 当对象的一个属性引用自身时,要避免相互引用造成死循环,如obj1.a=obj1
// 可使用 WeakMap 来存储这个引用自身的属性
weakmap.set(obj,res);
// 拷贝对象上的可遍历属性
for(let key in obj){
res[key]=deepCopy2(obj[key],weakmap);
}
return res;
}
改进后的深拷贝函数基本上可以满足使用要求。注意其中几点:
(1)借助一个 WeakMap
结构 weakmap,存储已复制的对象,这样在复制引用自身的属性时,直接返回 weakmap 中已有的对象,从而避免陷入死循环。(深拷贝存在递归复制,存在函数嵌套,所以使用 WeakMap 而不是 Map —— WeakMap 的键名所指向的对象,不计入垃圾回收机制。)
(2)特殊类型 Date 、RegExp,如果直接通过以下两行代码复制, Date类型属性值是代码执行时的时间,而不是原始对象对应属性值;对于 RegExp类型属性值,结果就是根本不符。所以对这两类特殊类型值,需要做特殊处理。
let cons=obj.constructor;
let res=new obj.constructor();
(3)通过上面这两行代码,保证拷贝后的对象类型与原始对象类型保持一致。
(4)诸如 error objects, functions, DOM nodes, and WeakMaps
这些值,是不可拷贝的。
最后,关于深浅拷贝的详细介绍,可看这篇文章。