浅析深拷贝的原理和实现

188 阅读3分钟

原理 

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

实现

 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;}