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