1、深拷贝

基础版

const deepClone = (obj) => {
  return JSON.parse(JSON.stringify(obj));
};
1
2
3

基础版缺点

  1. 函数、undefined、symbol经过JSON.stringify后会消失;
  2. Date类型变成字符串;
  3. 无法拷贝不可枚举的属性,即enumerable为false的属性;
  4. 无法拷贝对象的原型链;
  5. RegExp类型会变成空对象;
  6. NaN、Infinity、-Infinity,经过JSON.stringify后会变成null;
  7. 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj),会直接报错。

遍历拷贝

const deepClone = (obj) => {
  const newObj = {};
  for (let key in obj) {
    if (typeof obj[key] === "object") {
      newObj[key] = deepClone(obj[key]);
    } else {
      newObj[key] = obj[key];
    }
  }
  return newObj;
};
1
2
3
4
5
6
7
8
9
10
11

遍历版缺点

  1. 不能拷贝不可枚举的属性,即enumerable为false的属性,以及Symbol类型;
  2. 只能拷贝一般的引用类型,对于Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
  3. 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj),会爆栈。

优化遍历拷贝

// 判断是否为复杂引用类型
const isRealObj = (obj) =>
  (typeof obj === "object" || typeof obj === "function") && obj !== null;

const deepClone = (obj, hash = new WeakMap()) => {
  // 处理Date对象
  if (obj.constructor === Date) {
    return new Date(obj);
  }
  // 处理RegExp对象
  if (obj.constructor === RegExp) {
    return new RegExp(obj);
  }
  // 利用WeakMap处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  // 获取对象所有属性的特性列表
  const allKeyValue = Object.getOwnPropertyDescriptors(obj);
  // 继承原型链
  const newObj = Object.create(Object.getPrototypeOf(obj), allKeyValue);
  // 利用WeakMap处理循环引用
  hash.set(obj, newObj);
  // 使用Reflect.ownKeys获取对象的所有属性数组,包括不可枚举属性
  for (let key of Reflect.ownKeys(obj)) {
    // 为复杂引用类型且不为function类型的话进行递归,并将同一个WeakMap传入
    newObj[key] =
      isRealObj(obj[key]) && typeof obj[key] !== "function"
        ? deepClone(obj[key], hash)
        : obj[key];
  }
  return newObj;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

优化点

  1. 使用Reflect.ownKeys方法取出不可枚举属性以及Symbol类型;
  2. 对于Date、RegExp类型,直接生成一个新的实例返回;
  3. 使用Object.getOwnPropertyDescriptors()获取所有属性及其特性,并使用Object.create()创建一个新对象,继承传入原对象的原型链;
  4. 使用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄漏,检测循环引用,遇到循环引用直接返回WeakMap存储的值。