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; 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 实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复复制。
递归实现深拷贝
通过递归遍历实现深拷贝,对于简单类型,进行直接复制;引用类型,递归复制它的每一个属性。
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); }
|