深拷贝


1.JSON转换

这是最简单的一种方式:

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

但这种方式有很大缺陷,由于它是依赖于JSON,因此它不支持JSON不支持的其他格式,通过JSON的官网可知,JSON只支持ObjectArrayStringNumberBooleanNull这几种数据类型,其他的比如FunctionUndefinedDateRegExp等数据类型都不支持。对于它不支持的数据都会直接忽略该属性。

另外,如果对象存在循环引用的情况,会导致栈溢出。


2.递归函数

通过JSON转换有那么多问题,那我们就需要自己手写一个深拷贝方法,最常用的就是通过递归实现。

(1) 基础版本

1
2
3
4
5
6
7
8
9
10
11
function clone(target) {
if (target instanceof Object) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};

这种方式只能拷贝最简单的对象,还没有考虑其他引用类型:如数组、函数等。

(2) 拷贝数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function clone(target) {
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
// 如果是数组
cloneTarget = [];
} else {
cloneTarget = {};
}
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}

(3) 拷贝函数

对于函数的拷贝其实是有争议的,我认为函数不应该有深拷贝,因为对于函数,绝大多数情况都是用来调用执行,很少用来操作函数对象,所以对于函数的拷贝,我认为只需要拷贝引用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function clone(target) {
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
cloneTarget = [];
} else if (target instanceof Function) {
// 如果是函数
cloneTarget = target;
} else {
cloneTarget = {};
}
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}

(4) 拷贝正则表达式

1
const re = /test/g;

一个正则表达式由模式和修饰符组成,/test/为正则模式,g为修饰符。拷贝一个正则表达式,只需获取这两部分即可。通过正则对象的source属性可以获取正则规则,flags属性可以获取修饰符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function clone(target) {
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
cloneTarget = [];
} else if (target instanceof Function) {
cloneTarget = target;
} else if (target instanceof RegExp){
// 如果是正则表达式
cloneTarget = new RegExp(target.source, target.flags);
} else {
cloneTarget = {};
}
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}

(5) 拷贝日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function clone(target) {
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
cloneTarget = [];
} else if (target instanceof Function) {
cloneTarget = target;
} else if (target instanceof RegExp) {
cloneTarget = new RegExp(target.source, target.flags);
} else if (target instanceof Date) {
// 如果是日期
cloneTarget = new Date(target);
} else {
cloneTarget = {};
}
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}

到目前为止,我们已经写出了一个可以使用的深拷贝函数,但是这个函数仍存在很多可以优化的地方。


3.进一步优化

(1) 忽略原型上的属性

我们在遍历对象属性的时候,使用的是for infor in会遍历包括原型上的所有可迭代属性,但是事实上我们不应该这么做。所以我们需要通过hasOwnProperty筛选出自身的属性。

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
function clone(target) {
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
cloneTarget = [];
} else if (target instanceof Function) {
cloneTarget = target;
} else if (target instanceof RegExp) {
cloneTarget = new RegExp(target.source, target.flags);
} else if (target instanceof Date) {
cloneTarget = new Date(target);
} else {
cloneTarget = {};
}
for (const key in target) {
// 筛选自身属性
if (target.hasOwnProperty(key)) {
cloneTarget[key] = clone(target[key]);
}
}
return cloneTarget;
} else {
return target;
}
}

(2) 循环引用问题

解决循环引用问题的关键点在于判断一个对象是否已经被拷贝过,如果拷贝过直接返回拷贝后的对象。所以我们需要一个东西帮我们记录,最好的方式就是map

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
let cache = new Map();
function clone(target) {
// 如果已经拷贝过,直接返回拷贝后的对象
if (cache.get(target)) {
return cache.get(target);
}
if (target instanceof Object) {
let cloneTarget = null;
if (target instanceof Array) {
cloneTarget = [];
} else if (target instanceof Function) {
cloneTarget = target;
} else if (target instanceof RegExp) {
cloneTarget = new RegExp(target.source, target.flags);
} else if (target instanceof Date) {
cloneTarget = new Date(target);
} else {
cloneTarget = {};
}
// 记录拷贝对象
cache.set(target, cloneTarget);
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = clone(target[key]);
}
}
return cloneTarget;
} else {
return target;
}
}


参考:

https://juejin.cn/post/6844903929705136141

https://juejin.cn/post/6889327058158092302