JS深拷贝
emer 发布于 2021-1-18 15:14 1525 次阅读
JSON.sringify 和 JSON.parse
这是JS实现深拷贝最简单的方法了,原理就是先将对象转换为字符串,再通过JSON.parse重新建立一个对象。 但是这种方法的局限也很多:
- 不能复制function、正则、Symbol
- 循环引用报错
- 相同的引用会被重复复制
我们依次看看这三点,我们测试一下这段代码:
let obj = { reg : /^asd$/, fun: function(){}, syb:Symbol('foo'), asd:'asd' }; let cp = JSON.parse(JSON.stringify(obj)); console.log(cp);
结果:
可以看到,函数、正则、Symbol都没有被正确的复制。
如果在JSON.stringify
中传入一个循环引用的对象,那么会直接报错
这段代码:
let obj = { asd:'asd' }; let obj2 = {name:'aaaaa'};
obj.ttt1 = obj2;
obj.ttt2 = obj2; let cp = JSON.parse(JSON.stringify(obj));
obj.ttt1.name = 'change';
cp.ttt1.name = 'change'; console.log(obj,cp);
在原对象 obj 中的 ttt1 和 ttt2 指向了同一个对象 obj2,那么我在深拷贝的时候,就应该只拷贝一次 obj2 ,下面我们看看运行结果:
我们可以看到(上面的为原对象,下面的为复制对象),原对象改变 ttt1.name 也会改变 ttt2.name ,因为他们指向相同的对象。
但是,复制的对象中,ttt1 和 ttt2 分别指向了两个对象。复制对象没有保持和原对象一样的结构。因此,JSON实现深复制不能处理指向相同引用的情况,相同的引用会被重复复制。
递归实现
JS原生的方法不能很好的实现深复制,那么我们就动手实现一个。
思想非常简单:对于简单类型,直接复制。对于引用类型,递归复制它的每一个属性。
我们需要解决的问题:
- 循环引用
- 相同引用
- 不同的类型(笔者仅实现了数组和对象的区分)
实现代码:
function deepCopy(target){ let copyed_objs = [];//此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象 function _deepCopy(target){ if((typeof target !== 'object')||!target){return target;} for(let i= 0 ;i<copyed_objs.length;i++){ if(copyed_objs[i].target === target){ return copyed_objs[i].copyTarget;
}
} let obj = {}; if(Array.isArray(target)){
obj = [];//处理target是数组的情况 }
copyed_objs.push({target:target,copyTarget:obj}) Object.keys(target).forEach(key=>{ if(obj[key]){ return;}
obj[key] = _deepCopy(target[key]);
}); return obj;
} return _deepCopy(target);
} 复制代码
copyed_objs 这个数组存放的是已经递归过的目标对象。在递归一个目标对象之前,我们应该检查这个数组,如果当前目标对象和 copyed_objs 中的某个对象相等,那么不对其递归。
这样就解决了循环引用和相同引用的问题。
我们虽然的确解决了深拷贝的大部分问题。不过很多细节还没有去处理。在生产环境,我们还是要使用lodash的cloneDeep。cloneDeep对每个数据类型都单独处理的非常好。比如ArrayBuffer什么的。我们都没有处理。
https://github.com/lodash/lodash/blob/master/cloneDeep.js lodash