js深拷贝
# JSON.stringify && JSON.parse
这是最简单的 js 实现深拷贝方式了,原理是先将对象转换为字符串,再通过 JSON.parse 重新建立一个对象。
但这种方式存在一定的局限性:
不能复制 Function、正则、Symbol 循环引用会报错 相同引用会被重复复制 根据以上三点我们来验证,实测代码如下:
let obj = {
func: function () {},
reg: /^abc$/,
syb: Symbol("demo"),
str: "abc",
};
let copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
打印结果:Function、正则和 Symbol 都没有被复制正确的值
如果 JSON.stringify 中传入一个循环引用的对象,就会报错:
我们来看看下面这段代码:
let obj1 = { name: "Mike" };
let obj2 = { age: 18 };
obj1.text1 = obj2; // {age:18}
obj1.text2 = obj2; // {age:18}
let copys = JSON.parse(JSON.stringify(obj1));
obj1.text1.age = 22;
copys.text1.age = 22;
console.log("原对象", obj1);
console.log("复制对象", copys);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
结果: 我们看到当原对象的 text1.age 改变时 text2.age 也会改变;因为它们指向相同的对象。
但是,在复制对象中 text1 和 text2 分别指向两个对象,复制对象没有保持和原对象一样的结构;所以可以得出:
JSON 实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复复制。
# 递归实现深拷贝
通过递归遍历实现深拷贝,对于简单类型,进行直接复制;引用类型,递归复制它的每一个属性。
function isObject(obj) {
return obj !== null && typeof obj === "object";
}
function _cloneDeep(target) {
if (!isObject(target)) return target;
let result = Array.isArray(target) ? [] : {};
const keys = Object.keys(target);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = cloneDeep(target[keys[i]]);
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
以上代码并没有考虑到循环引用和相同引用的问题;对于循环引用的对象使用的话会直接栈溢出,会出现和 JSON 方法一样的问题。
# 解决方法和思路:
1、通过闭包维护一个变量,变量中存储已经遍历过的对象
2、每次递归时判断当前的参数是否已经存在于变量中;如果已经存在,说明已经递归过该对象,就停止这次递归并返回上次递归该对象时的返回值
代码实现如下:
function isObject(obj) {
// 判断类型
return obj !== null && typeof obj === "object";
}
function _cloneDeep(obj) {
let visitedObjs = [];
function baseClone(target) {
if (!isObject(target)) return target;
for (let i = 0; i < visitedObjs.length; i++) {
if (visitedObjs[i].target === target) {
return visitedObjs[i].result;
}
}
let result = Array.isArray(target) ? [] : {};
visitedObjs.push({ target, result });
const keys = Object.keys(target);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = baseClone(target[keys[i]]);
}
return result;
}
return baseClone(obj);
}
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
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
上次更新: 2022/09/06, 10:30:04