如何实现一个深拷贝

深拷贝的简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function cloneDeep(source, hash = new WeakMap()) {
if (source === null) return null;
if (source === undefined) return undefined;
if (source instanceof RegExp) return new RegExp(source);
if (source instanceof Date) return new Date(source);
if (typeof source !== "object") return source;
if (hash.has(source)) return hash.get(source);
let target = new source.__proto__.constructor;
hash.set(source, target);
for (let key in source)
if (Object.prototype.hasOwnProperty.call(source, key))
target[key] = cloneDeep(source[key], hash);
return target;
}

特殊对象的拷贝例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function person(pname) {
this.name = pname;
}
const Bob = new person('Bob');
var a = {
name: "cxs",
book: {
title: "You Don't Know JS",
price: "45"
},
a1: undefined,
a2: null,
a3: new Date(),
a4: new RegExp('ab+c', 'i'),
a5: /^123$/,
a6: [1, 2, 3],
a7: function() {
console.log("123");
},
a8: Bob
}
var b = cloneDeep(a);
console.log(a);
console.log(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
{
name: 'cxs',
book: { title: "You Don't Know JS", price: '45' },
a1: undefined,
a2: null,
a3: 2020-10-27T11:13:03.510Z,
a4: /ab+c/i,
a5: /^123$/,
a6: [ 1, 2, 3 ],
a7: [Function: a7],
a8: person { name: 'Bob' }
}
{
name: 'cxs',
book: { title: "You Don't Know JS", price: '45' },
a1: undefined,
a2: null,
a3: 2020-10-27T11:13:03.510Z,
a4: /ab+c/i,
a5: /^123$/,
a6: [ 1, 2, 3 ],
a7: [Function: a7],
a8: person { name: 'Bob' }
}

可以看到null、undefined、日期对象、正则对象、数组对象、普通函数对象、构造函数的实例对象都能被正确拷贝。
下面来尝试修改一下里面的数据,看是否达到了深拷贝的效果:

1
2
3
4
5
a.name = "lxm";
a.book.price = "55";
a.a6[0] = 4;
console.log(a);
console.log(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
{
name: 'lxm',
book: { title: "You Don't Know JS", price: '55' },
a1: undefined,
a2: null,
a3: 2020-10-27T11:20:01.590Z,
a4: /ab+c/i,
a5: /^123$/,
a6: [ 4, 2, 3 ],
a7: [Function: a7],
a8: person { name: 'Bob' }
}
{
name: 'cxs',
book: { title: "You Don't Know JS", price: '45' },
a1: undefined,
a2: null,
a3: 2020-10-27T11:20:01.590Z,
a4: /ab+c/i,
a5: /^123$/,
a6: [ 1, 2, 3 ],
a7: [Function: a7],
a8: person { name: 'Bob' }
}

可以看到,修改原始对象,不影响拷贝对象,因此是深拷贝。

循环引用

1
2
3
4
5
let oldObj = {};
oldObj.a = oldObj;
let newObj = cloneDeep(oldObj);
console.log(oldObj);
console.log(newObj);

结果:

1
2
{ a: [Circular] }
{ a: [Circular] }

我们解决循环引用所采用的方法是使用哈希表,其实就是循环检测,设置哈希表存储已拷贝过的对象,当检测到当前对象已存在于哈希表中时,取出该值并返回即可。除了用哈希表,也可以用数组。
下面来看看我们的深拷贝函数是否存在引用丢失的情况:

1
2
3
4
5
6
7
let obj1 = {};
let obj2 = {a: obj1, b: obj1};
console.log(obj2.a === obj2.b);
// true
let obj3 = cloneDeep(obj2);
console.log(obj3.a === obj3.b);
// true

obj2 的键值 a 和 b 同时引用了同一个对象 obj1,使用 cloneDeep 进行深拷贝后没有出现引用丢失的情况,因为我们使用了哈希表存储了已拷贝过的对象。

不足

我们实现的深拷贝函数依然是不完善的,比如Symbol、Buffer对象、Promise、Set、Map等也都需要我们做特殊处理,而且由于使用了递归,可能会出现爆栈的问题,破解递归爆栈的方法是使用循环拷贝,详见木易杨——实现深拷贝

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2021 苏御
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信