# 一. 基础

  • 浅拷贝 : 只是将数据中所有的数据引用下来,依旧指向同一个存放地址,拷贝之后的数据修改之后,也会影响到原数据的中的对象数据。

    另外一种说法:只拷贝第一层的原始类型值,和第一层的引用类型地址。如对象的 Object.assign(),数组的 Array.prototype.slice()Array.prototype.concat(),ES6 的 扩展运算符

  • 深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

# 二. 浅拷贝实现

# 实现一:

function clone(target) {
  let cloneTarget = {};
  for (const key in target) {
    cloneTarget[key] = target[key];
  }
  return cloneTarget;
}
1
2
3
4
5
6
7

# 三. 深拷贝实现

# 实现 1:普通版

JSON.parse(JSON.stringify());
1

# 实现 2:考虑普通的 object

function clone(target) {
  if (typeof target === "object") {
    let cloneTarget = {};
    for (const key in target) {
      cloneTarget[key] = clone(target[key]);
    }
    return cloneTarget;
  } else {
    return target;
  }
}
// 测试
const target = {
  field1: 1,
  field2: undefined,
  field3: "ConardLi",
  field4: {
    child: "child",
    child2: {
      child2: "child2",
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

缺点: 没有考虑到数组

# 实现 3:考虑数组

module.exports = function clone(target) {
  if (typeof target === "object") {
    let cloneTarget = Array.isArray(target) ? [] : {};
    for (const key in target) {
      cloneTarget[key] = clone(target[key]);
    }
    return cloneTarget;
  } else {
    return target;
  }
};
1
2
3
4
5
6
7
8
9
10
11

缺点: 没有考虑对象存在循环引用的情况,递归进入死循环导致栈内存溢出

# 实现 4:循环引用

额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝。

这个存储空间,需要可以存储 key-value 形式的数据,且 key 可以是一个引用类型,我们可以选择 Map 这种数据结构:

  • 检查 map 中有无克隆过的对象
  • 有 - 直接返回
  • 没有 - 将当前对象作为 key,克隆对象作为 value 进行存储
  • 继续克隆
function clone(target, map = new Map()) {
  if (typeof target === "object") {
    let cloneTarget = Array.isArray(target) ? [] : {};
    if (map.get(target)) {
      return map.get(target);
    }
    map.set(target, cloneTarget);
    for (const key in target) {
      cloneTarget[key] = clone(target[key], map);
    }
    return cloneTarget;
  } else {
    return target;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

改进:使用WeakMap,弱引用。弱引用对象,垃圾回收机制会自动回收。

function clone(target, map = new WeakMap()) {
  // ...
}
1
2
3

# 实现 5:可继续遍历的类型,MapSet

function getType(target) {
  return Object.prototype.toString.call(target);
}
function getInit(target) {
  const Ctor = target.constructor;
  return new Ctor();
}

function clone(target, map = new WeakMap()) {
  // 克隆原始类型
  if (!isObject(target)) {
    return target;
  }

  // 初始化
  const type = getType(target);
  let cloneTarget;
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type);
  }

  // 防止循环引用
  if (map.get(target)) {
    return map.get(target);
  }
  map.set(target, cloneTarget);

  // 克隆set
  if (type === setTag) {
    target.forEach((value) => {
      cloneTarget.add(clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆对象和数组
  const keys = type === arrayTag ? undefined : Object.keys(target);
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value;
    }
    cloneTarget[key] = clone(target[key], map);
  });

  return cloneTarget;
}
const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: "child",
  },
  field4: [2, 4, 8],
  empty: null,
  map,
  set,
};
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 实现 6:不可继续遍历的类型

Bool,Number,String,Date,Error类型:

function cloneOtherType(targe, type) {
  const Ctor = targe.constructor;
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(targe);
    case regexpTag:
      return cloneReg(targe);
    case symbolTag:
      return cloneSymbol(targe);
    default:
      return null;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

克隆Symbol类型:

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

克隆正则:

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

1
2
3
4
5
6
7
8
9
10
11
12
13

# 实现 7:克隆函数

function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();
  if (func.prototype) {
    console.log("普通函数");
    const param = paramReg.exec(funcString);
    const body = bodyReg.exec(funcString);
    if (body) {
      console.log("匹配到函数体:", body[0]);
      if (param) {
        const paramArr = param[0].split(",");
        console.log("匹配到参数:", paramArr);
        return new Function(...paramArr, body[0]);
      } else {
        return new Function(body[0]);
      }
    } else {
      return null;
    }
  } else {
    return eval(funcString);
  }
}
const map = new Map();
map.set("key", "value");
map.set("ConardLi", "code秘密花园");

const set = new Set();
set.add("ConardLi");
set.add("code秘密花园");

const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: "child",
  },
  field4: [2, 4, 8],
  empty: null,
  map,
  set,
  bool: new Boolean(true),
  num: new Number(2),
  str: new String(2),
  symbol: Object(Symbol(1)),
  date: new Date(),
  reg: /\d+/,
  error: new Error(),
  func1: () => {
    console.log("code秘密花园");
  },
  func2: function(a, b) {
    return a + b;
  },
};
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 四.参考

[1]如何写出一个惊艳面试官的深拷贝? (opens new window)

[2]深拷贝的终极探索(90%的人都不知道) (opens new window)