JSON.stringify && JSON.parse
这是最简单的 js 实现深拷贝方式了,原理是先将对象转换为字符串,再通过 JSON.parse 重新建立一个对象。
但这种方式存在一定的局限性:
不能复制 Function、正则、Symbol
循环引用会报错
相同引用会被重复复制
根据以上三点我们来验证,实测代码如下:
| 12
 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 中传入一个循环引用的对象,就会报错:
我们来看看下面这段代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | let obj1 = { name: "Mike" };let obj2 = { age: 18 };
 
 obj1.text1 = obj2;
 obj1.text2 = obj2;
 
 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 实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复复制。
递归实现深拷贝
通过递归遍历实现深拷贝,对于简单类型,进行直接复制;引用类型,递归复制它的每一个属性。
| 12
 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、每次递归时判断当前的参数是否已经存在于变量中;如果已经存在,说明已经递归过该对象,就停止这次递归并返回上次递归该对象时的返回值
代码实现如下:
| 12
 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);
 }
 
 |