JSON.stringify && JSON.parse

这是最简单的 js 实现深拷贝方式了,原理是先将对象转换为字符串,再通过 JSON.parse 重新建立一个对象。

但这种方式存在一定的局限性:

不能复制 Function、正则、Symbol
循环引用会报错
相同引用会被重复复制
根据以上三点我们来验证,实测代码如下:

1
2
3
4
5
6
7
8
9
let obj = {
func: function () {},
reg: /^abc$/,
syb: Symbol("demo"),
str: "abc",
};

let copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj);

打印结果:Function、正则和 Symbol 都没有被复制正确的值

如果 JSON.stringify 中传入一个循环引用的对象,就会报错:

我们来看看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

结果:
我们看到当原对象的 text1.age 改变时 text2.age 也会改变;因为它们指向相同的对象。

但是,在复制对象中 text1 和 text2 分别指向两个对象,复制对象没有保持和原对象一样的结构;所以可以得出:

JSON 实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复复制。


递归实现深拷贝

通过递归遍历实现深拷贝,对于简单类型,进行直接复制;引用类型,递归复制它的每一个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

以上代码并没有考虑到循环引用和相同引用的问题;对于循环引用的对象使用的话会直接栈溢出,会出现和 JSON 方法一样的问题。

解决方法和思路:

1、通过闭包维护一个变量,变量中存储已经遍历过的对象

2、每次递归时判断当前的参数是否已经存在于变量中;如果已经存在,说明已经递归过该对象,就停止这次递归并返回上次递归该对象时的返回值

代码实现如下:

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
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);
}