注册
web

深拷贝的终极实现

引子



通过本文可以学习到深拷贝的三种写法的实现思路与性能差异



首先,我们要理解什么是深拷贝,以及为什么要实现深拷贝


深拷贝是什么


通俗来讲,深拷贝就是深层的拷贝一个变量值;


为什么要实现深拷贝


因为在拷贝引用值时,由于复制一个变量只是将其指向要复制变量的引用内存地址,他们并没有完全的断开,而使用就可以实现深拷贝将其完全拷贝为两个单独的存在,指向不同的内存地址;


如何实现深拷贝


一行实现


let deepClone = JSON.parse(JSON.stringify(obj))

这种是最简单的实现方法,虽然这个方法适用于常规,但缺点是无法拷贝 Date()或是RegExp()
 


简单实现


function deepClone(obj) {
// 判断是否是对象
if (typeof obj !== 'object') return obj
// 判断是否是数组 如果是数组就返回一个新数组 否则返回一个新对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj
for (var key in obj) {
// 将key值拷贝,再层层递进拷贝对象的值
newObj[key] = deepClone(obj[key]);
}
// 返回最终拷贝完的值
return newObj;
}

对于普通的值(如数值、字符串、布尔值)和常见的引用类型(如对象和数组),这个写法完全够用。


但是这个写法有个缺陷,就是无法正确拷贝 Date()  和  RegExp()  等实例对象,因为少了对这些引用类型的特殊处理


普通版


function deepClone(origin, target) {
let tar = target || {};
for (var key in origin) {
if (origin.hasOwnProperty(key)) {
if (typeof origin[key] === 'object' && origin[key] !== null) {
tar[key] = Array.isArray(origin[key]) ? [] : {};
deepClone(origin[key], tar[key]);
} else {
tar[key] = origin[key];
}
}
}
return tar;
}

这个深拷贝方法通过判断属性的值类型,实现了对 对象数组 以及 DateRegExp 等引用类型对象的递归拷贝,同时也考虑了拷贝基本类型值的情况,能够满足大多数场景的要求。


最终版


为什么还有最终版?

上面的案例,可以应对一般场景。


但是对于有两个对象相互拷贝的场景,会导致循环的无限递归,造成死循环!



Uncaught RangeError: Maximum call stack size exceeded



场景:


image.png


如何解决无限递归的问题?

首先我们要了解 WeakMap()
WeakMap的键名所指向的对象,不计入垃圾回收机制;


而通过 WeakMap 记录已经拷贝过的对象,能防止循环引用导致的无限递归;



WeakMap 的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用



代码

利用 WeakMap() 在属性遍历完绑定,并在每次循环时获取当前键名,如果存在则返回数据,不存在则拷贝


function deepClone(origin, hashMap = new WeakMap()) {
// 判断是否是对象
if (origin == undefined || typeof origin !== 'object') return origin;
// 判断是否是Date类型
if (origin instanceof Date) return new Date(origin);
if (origin instanceof RegExp) return new RegExp(origin);

// 判断是否是数组
const hashKey = hashMap.get(origin);
// 如果是数组
if (hashKey) return hashKey;

// 从原型上复制一个值
// *:利用原型构造器获取新的对象 如: [], {}
const target = new origin.constructor();
// 将对象存入map
hashMap.set(origin, target);
// 循环遍历当前层数据
for (let k in origin) {
// 判断当前属性是否为引用类型
if (origin.hasOwnProperty(k)) {
target[k] = deepClone(origin[k], hashMap);
}
}
return target;
}

我们再来看一下使用最新版后的两个对象互相拷贝:


image.png


可以看到,通过使用 WeakMap 记录已经拷贝的对象,有效防止循环引用导致的栈溢出错误,是功能最完备的深拷贝实现。


总结


深拷贝可以完全拷贝一个对象,生成两个独立的且相互不影响的对象。


明白各种深拷贝实现的思路和性能差异,可以在不同场景选用最优的方案。


作者:Shrimpsss
来源:juejin.cn/post/7226181917997547576

0 个评论

要回复文章请先登录注册