原理
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
实现
1. JSON.stringfy
JSON.stringfy() 是目前开发过程中最简单的深拷贝方法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象
注意点
①拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
② 拷贝 Date 引用类型会变成字符串;
③无法拷贝不可枚举的属性;
④无法拷贝对象的原型链;
⑤拷贝 RegExp 引用类型会变成空对象;
⑥对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
⑦无法拷贝对象的循环应用,即对象成环 (obj[key] = obj);
⑧使用 JSON.stringify 方法实现深拷贝对象,虽然到目前为止还有很多无法实现的功能,但是这种方法足以满足日常的开发需求,并且是最简单和快捷的。而对于其他的也要实现深拷贝的,比较麻烦的属性对应的数据类型,JSON.stringify 暂时还是无法满足的
2. 手写递归实现
通过 for in 遍历传入参数的属性值,如果值是引用类型则再次递归调用该函数,如果是基础数据类型就直接复制;
注意点
①这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
② 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
③ 对象的属性里面成环,即循环引用没有解决。
3. 改进后递归实现
①针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;
② 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;
③ 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链;
④ 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。
WeakMap 拓展
1、WeakMap只接受对象作为key,如果设置其他类型的数据作为key,会报错。 2、WeakMap的key所引用的对象都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。 3、由于WeakMap的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。 4、没有clear()方法 5、不能遍历
现实代码实现
// 深拷贝export function deepClone(target) { // 定义一个变量 let results; // 如果当前需要深拷贝的是一个对象的话 if (typeof target === 'object') { // 如果是一个数组的话 if (Array.isArray(target)) { results = []; // 将result赋值为一个数组,并且执行遍历 target.map(val => { results.push(deepClone(val)); return false; }); // 判断如果当前的值是null的话;直接赋值为null } else if (target === null) { results = null; // 判断如果当前的值是一个RegExp对象的话,直接赋值 } else if (target.constructor === RegExp) { results = target; } else { // 否则是普通对象,直接for in循环,递归赋值对象的所有值 results = {}; for (const i in target) { if (target.hasOwnProperty(i)) { results[i] = deepClone(target[i]); } } } // 如果不是对象的话,就是基本数据类型,那么直接赋值 } else { results = target; } // 返回最终结果 return results;}