js对象拷贝
js的内存结构
原始类型与引用类型
六大原始数据类型:String、Number、Boolean、Null、Undefined、Symbol。对这些简单数据类型(原始值)的访问是按值访问的,因此我们操作的就是存储在变量中的实际值。
引用数据类型:object。保存引用值得变量时按引用访问的,不能直接操作对象所在内存空间,在操作对象时,实际上操作的时对该对象的引用,而非实际的对象本身。
可以认为原始类型,是以键值对的形式存储在栈中。而引用类型的只是将地址存储在栈中,对象本身存储到堆内存中了,我们无法直接访问。
-
对对象复制(浅拷贝)
当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。
var oldobj = {
name: '小二上酒',
age: 22,
arr: [1, 2, 3, { key: '123' }], //数组测试
};
let newobj = oldobj
newobj.name = '不喝酒';
console.log(oldobj);
console.log(newobj);
可以看到,浅拷贝只不过是将oldobj地址复制了一份传给newobj,他们实际上指的还是同一个内存中的对象。操作newobj指向的对象时,即也在操作lodobj指向的对象,总而言之对象还是那个对象。
-
深拷贝
我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。深拷贝则是,完完全全将栈内存中存放的地址和堆内存中的对象都拷贝(创建)一份,生成一个新的对象。既然属性值类型是数组和或象时只会传地址址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可
1 function deepCopy(target={}){ 2 if(typeof target !=='object' || target == null){ 3 return target 4 } 5 let result 6 if(target instanceof Array){ 7 result =[] 8 }else{ 9 result={} 10 } 11 for(let key in target){ 12 result[key] = deepCopy(target[key]) 13 } 14 return result 15 } 16 17 var oldobj = { 18 name: '小二上酒', 19 age: 22, 20 arr: [1, 2, 3, { key: '123' }], //数组测试 21 }; 22 let newobj = deepCopy(oldobj) 23 newobj.name = '不喝酒'; 24 console.log(oldobj); 25 console.log(newobj); //
可以看到这样一来对复制后得到的newobj对象进行修改,就不会影响到oldobj了。
但是这样对一些特殊的情况并不适用。我们还需要解决以下几个问题
-
对象内属性循环引用自身
-
相同的引用
-
其余类型
解决前俩点的完善写法
1 2 function deepCopy(target) { 3 let copyed_objs = [];//此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象 4 5 function _deepCopy(target) { 6 if ((typeof target !== 'object') || !target) { 7 return target; 8 } 9 // 如果当前目标对象和 copyed_objs 中的某个对象相等,那么不对其递归。 10 for (let i = 0; i < copyed_objs.length; i++) { 11 if (copyed_objs[i].target === target) { 12 return copyed_objs[i].copyTarget; 13 } 14 } 15 //处理target是数组的情况 16 let result; 17 if (target instanceof Array) { 18 result = []; 19 } else { 20 result = {} 21 } 22 copyed_objs.push({ target: target, copyTarget: result }) 23 24 // 递归实现深层次拷贝 25 for(let key in target){ 26 result[key] = _deepCopy(target[key]) 27 } 28 return result; 29 } 30 return _deepCopy(target); 31 } 32 33 34 var oldobj = { 35 name: '小二上酒', 36 age: 22, 37 arr: [1, 2, 3, { key: '123' }], //数组测试 38 }; 39 40 oldobj.self = oldobj //循环引用测试 41 oldobj.likes = { book: '剑来' } 42 oldobj.hobby = oldobj.likes //相同引用测试 43 let newobj = deepCopy(oldobj) 44 console.log(oldobj); 45 console.log(newobj);
可以看出,尽管对oldobj添加循环引用自身的属性,以及相同引用的属性,依然可以完成深拷贝。
2022.10.30 利用weakMap弱引用特性存储已经循环过的target. 简化处理环
//weakmap存放的对象target将会是弱引用
1 function deepClone(target, map = new WeakMap()) { 2 if (typeof target !== 'object') { 3 return target 4 } 5 let result = new target.constructor() 6 // debugger 7 if (map.get(target)) 8 return map.get(target) //存入的对象值发生变化会同步更新存进map的对象值 9 10 map.set(target, result) 11 12 Object.keys(target).forEach((key) => { 13 result[key] = deepClone(target[key], map) 14 }) 15 return result 16 }
ps:JSON.sringify 和 JSON.parse
1 var oldobj = { 2 name: '小二上酒', 3 age: 22, 4 arr: [1, 2, 3, { key: '123' }], //数组测试 5 }; 6 let newobj = JSON.parse(JSON.stringify(oldobj)) 7 console.log(oldobj); 8 console.log(newobj);
这是JS实现深拷贝最简单的方法了,原理就是先将对象转换为字符串,再通过JSON.parse重新建立一个对象。适合项目中快捷使用。 但是这种方法的局限也很多:
- 不能复制function、正则、Symbol
- 循环引用报错
- 相同的引用会被重复复制
学习于:https://juejin.cn/post/6844903620190666759
https://www.cnblogs.com/web1/p/6518482.html