深拷贝和浅拷贝
浅拷贝: 对象只被克隆了一个引用或者只会被克隆最外部的一层,更深层的对象还是通过引用指向同一块堆内存。
1. Object.assign()
1 let p1= { 2 name: 'Tom' , 3 children: [{ 4 name: 'A' 5 }] 6 }; 7 let p2 = Object.assign({}, p1); 8 p1.name = 'Tim'; 9 console.log(p2.name); // Tom 10 p1.children[0].name = 'B'; 11 console.log(p2.children[0].name); // B
2. 展开运算符 ...
1 let p1 = { 2 name: 'Tom', 3 children: [{ 4 name: 'A' 5 }] 6 }; 7 let p2 = { ...p1 }; 8 p1.name = 'Tim'; 9 console.log(p1.name); // Tom
3. 自己编写一个函数
1 function shallowClone(obj) { 2 const result = {}; 3 for (let key in obj) { 4 result[key] = obj[key]; 5 } 6 return obj; 7 }
深拷贝:不光解决了第一层的问题,还递归拷贝了目标对象的所有属性。
1. JSON.parse(JSON.strigify())
1 function Person(name) { 2 this.name = name; 3 } 4 const p = new Person('Tom'); 5 6 const oldObj = { 7 a: function() { console.log('hi');}, 8 b: new Array(1), 9 c: new RegExp('ab+c', 'i'), 10 d: p, 11 e: Symbol('a'), 12 f: undefined 13 }; 14 15 const newObj = JSON.parse(JSON.stringify(oldObj)); 16 17 console.log(oldObj); 18 //{ 19 a: ƒ (), 20 b: [empty], 21 c: /ab+c/i, 22 d: Person {name: "Tom"}, 23 e: Symbol(male), 24 f: undefined 25 } 26 console.log(newObj); 27 //{ 28 b: [null], 29 c: {}, 30 d: {name: "Tom"} 31 }
缺点:
- 无法实现对函数、undefined、regexp、symbol等类型克隆
- 会抛弃对象的constructor,所有的构造函数都会指向object
- 对象有循环引用会报错
2. 简单的深拷贝(不考虑函数、Date、RegExp等类型)
1 const deepClone = (obj) => { 2 if (obj === null || typeof obj !== 'object') return obj; 3 let cloneObj = Array.isArray(obj) ? [] : {}; 4 for(let key in obj) { 5 if (!obj.hasOwnProperty(key)) return; 6 cloneObj[key] = obj[key] !== null && typeof obj === 'object' ? deepClone(obj[key]) : obj[key]; 7 } 8 return cloneObj; 9 }
3. 考虑部分类型的简易版深拷贝,推荐使用 lodash 的深拷贝函数。
1 const isType = (obj, type) => Object.prototype.toString.call(obj).slice(8, -1) === type; 2 3 // 可用es6的flags替换(ie不支持) 4 const getRegExp = re => { 5 var result = ''; 6 if (re.global) result += 'g'; 7 if (re.ignoreCase) result += 'I'; 8 if (re.multiline) result += 'm'; 9 return result; 10 } 11 12 const deepClone = (obj) => { 13 const oldList = []; 14 const newList = []; 15 const _clone = (parent) => { 16 if (parent === null || typeof parent !== 'object') return parent; 17 let result; 18 if (isType(parent, 'Array')) { 19 result = []; 20 } else if (isType(parent, 'RegExp')) { 21 result = new RegExp(parent.source, getRegExp(parent)); // 等同于 parent.flags 22 if (parent.lastIndex) resut.lastIndex = parent.lastIndex; 23 } else if (isType(parent, 'Date')) { 24 result = new Date(parent.getTime()); 25 } else { 26 result = Object.create(Object.getPrototypeOf(parent)); 27 } 28 // 处理循环引用 29 const index = oldList.indexOf(parent); 30 if (index !== -1) { 31 return newList[index]; 32 } 33 oldList.push(parent); 34 newList.push(result); 35 36 for (let attr in parent) { 37 // 递归调用 38 result[attr] = _clone(parent[attr]); 39 } 40 return result; 41 }; 42 return _clone(obj); 43 }
4. MessageChannel
1 // 若所需拷贝的对象含有内置类型并且不包含函数,可以使用MessageChannel 2 3 const structuralClone = obj => { 4 return new Promise(resolve => { 5 const {port1, port2} = new MessageChannel(); 6 port2.onmessage = ev => resolve(ev.data); 7 port1.postMessage(obj); 8 }); 9 }; 10 11 const test = async () => { 12 const clone = await structuralClone(obj); 13 console.log(clone); 14 }; 15 test();