深拷贝VS浅拷贝
- 深拷贝和浅拷贝针对的是引用类型
- JS中分为值类型和引用类型,当赋值类型是引用类型,实际是对地址的拷贝,两个变量指向的同一份数据
- 对于其中一个进行修改,同时会影响到另外一个
浅拷贝
浅拷贝也就是拷贝原对象里面的数据,但是不拷贝原对象里面的子对象,也就是会克隆出一个对象,数据相同,但是子对象的引用地址不会被拷贝,子对象引用地址与原子对象相同。
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 29 30 31 32 33 34 35 36 37 38 39 40
| const obj1 = { name:'Bob', car: { brand: "Wuling", color: "White" }, getAge:function(){ console.log('24'); } }
const obj2 = obj1; obj2 obj2 === obj1
function shallowClone(obj) { const target = {}; for(var key in obj) { if (obj.hasOwnProperty(key)) { target[key] = obj[key]; } } return target; }
obj3 = shallowClone(obj1); obj3 obj3 === obj1; obj3.name = "Alice"; obj3 obj1
obj3.car.color = "Red" obj3.car obj1.car obj2.car
|
深拷贝
- 深拷贝只能拷贝首层数据,更深层次的数据依然指向原来的地址
- 常用的浅拷贝有两种方法
- JSON.parse(JSON.stringify(Object))
- 遍历对象属性
JSON.parse(JSON.stringify(Object))
该种方法中 undefined、function、symbol 会在转换过程中被忽略。
1 2 3 4 5 6 7 8 9
| const obj1 = { name: 'Bob', getAge: function(){ console.log('24'); } } console.log(obj1); const obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj2);
|
遍历对象属性
可以通过遍历的方法解决部分类型被忽略的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function deepClone(obj) { const target = {}; for(var key in obj) { if (obj.hasOwnProperty(key)) { if(typeof key === 'object') { deepClone(obj[key]); } else { target[key] = obj[key]; } } } return target; }
const obj3 = shallowClone(obj1); console.log(obj3); obj3 === obj1; obj3.name = "Alice"; console.log(obj3);
|
上面的方法并没有对传入参数进行校验,若传入对值为 null,则应该直接返回null
同时要考虑对数组对支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function deepClone(obj) { if(typeof obj !== 'object' || obj === null) { return obj; } const target = Array.isArray(obj) ? [] : {}; for(var key in obj) { if (obj.hasOwnProperty(key)) { if(typeof key === 'object' && obj !== null) { deepClone(obj[key]); } else { target[key] = obj[key]; } } } return target; }
|
循环引用
使用哈希表
设置一个数组或者哈希表存储已经拷贝过对对象,当检测到对象已经存在哈利表中,取出该值即可。
使用到了,ES6 WeakMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function deepClone(obj, hash = new WeakMap()) { if(typeof obj !== 'object' || obj === null) { return obj; } if(hash.has(obj)){ return hash.get(obj); } const target = Array.isArray(obj) ? [] : {}; hash.set(obj, target); for(var key in obj) { if (obj.hasOwnProperty(key)) { if(typeof key === 'object' && obj !== null) { deepClone(obj[key], hash); } else { target[key] = obj[key]; } } } return target; }
|
使用数组
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 29 30 31 32
| function deepClone(obj, uniqueList = []) { if(typeof obj !== 'object' || obj === null) { return obj; } uniqueList ? "" : uniqueList = []; const target = Array.isArray(obj) ? [] : {}; if( uniqueList.length > 0 ) { for(var i = 0; i < uniqueList.length; i++) { if (uniqueList[i].obj === obj) { return uniqueList[i]; } } } else { return null; } for(var key in obj) { if (obj.hasOwnProperty(key)) { if(typeof key === 'object' && obj !== null) { deepClone(obj[key], uniqueList); } else { target[key] = obj[key]; } } } return target; }
|
递归爆栈
顾名思义,防止程序无限递归,导致报错
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 29 30 31 32 33 34 35 36 37 38 39
| function deepClone(obj, uniqueList = []) { const root = {}; const loopList = [ { parent: root, key: undefined, data: x } ]; while(loopList.length) { const node = loopList.pop(); const parent = node.parent; const key = node.key; const data = node.data; let res = parent; if (typeof key !== 'undefined') { res = parent[key] = {}; } for (let k in data) { if (data.hasOwnProperty(k)) { if (typeof data[k] === 'object') { loopList.push({ parent: res, key: k, data: data[k], }); } else { res[k] = data[k]; } } } } return root; }
|
参考
深拷贝的终极探索(99%的人都不知道)