浅拷贝与深拷贝的实现及深拷贝的循环引用问题
一、浅拷贝
浅拷贝只会将被复制对象的第一层属性进行复制,若第一层属性为原始类型的值,则直接复制其值,一般称之为“传值”;若第一层属性为引用类型的值,则复制的是其存储的指向堆内存对象的地址指针,一般称之为“传址”。因此浅拷贝的结果存在当改变一个对象的值时引起另一个对象值变化的问题。即新对象和旧对象之间值相互影响。下面是一些实现浅拷贝的方法:
(1)Array.concat(),Array.slice(),Array.from() 等
let a = [1, 2, 3, {name: 'Joy'}]; let b = [].concat(a); let c = b.slice(); let d = Array.from(c); a[1] = 1000; console.log('a: ', a); //a: [ 1, 1000, 3, { name: 'Joy' } ] console.log('b: ', b); //b: [ 1, 2, 3, { name: 'Joy' } ] console.log('c: ', c); //c: [ 1, 2, 3, { name: 'Joy' } ] console.log('d: ', d); //d: [ 1, 2, 3, { name: 'Joy' } ] /**---改变a中的原始类型值,b、c、d没有变化 */ a[3].name = 'Bob'; console.log('a: ', a); //a: [ 1, 1000, 3, { name: 'Bob' } ] console.log('b: ', b); //b: [ 1, 2, 3, { name: 'Bob' } ] console.log('c: ', c); //c: [ 1, 2, 3, { name: 'Bob' } ] console.log('d: ', d); //d: [ 1, 2, 3, { name: 'Bob' } ] /**---改变a中的引用类型值,发现b、c、d中引用类型值也发生了变化 */
(2)Object.assign()
let a = { name: 'Joy', friends: ['Bob', 'Tom', 'Jim'] }; let b = Object.assign({}, a); a.name = 'Lie'; console.log('a: ', a); //a: { name: 'Lie', friends: [ 'Bob', 'Tom', 'Jim' ] } console.log('b: ', b); //b: { name: 'Joy', friends: [ 'Bob', 'Tom', 'Jim' ] } /**---改变a中的原始类型值,b没有变化 */ a.friends.push('Sun'); console.log('a: ', a); //a: { name: 'Lie', friends: [ 'Bob', 'Tom', 'Jim', 'Sun' ] } console.log('b: ', b); //b: { name: 'Joy', friends: [ 'Bob', 'Tom', 'Jim', 'Sun' ] } /**---改变a中的引用类型值,发现b中引用类型值也发生了变化 */
还有例如 es6扩展运算符 等方法也实现的是浅拷贝。
二、深拷贝
而不同于浅拷贝,深拷贝是逐层对目标对象进行复制,意味着会在栈内存中重新分配空间存储指向一个新对象的新地址指针,因此不存在改变一个对象值而引发另一个对象随之改变的问题。
1、使用递归的方式实现深拷贝
//使用递归的方式实现数组、对象的深拷贝 function deepClone1(obj) { //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝 var objClone = Array.isArray(obj) ? [] : {}; //进行深拷贝的不能为空,并且是对象或者是 if (obj && typeof obj === "object") { for (key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] === "object") { objClone[key] = deepClone1(obj[key]); } else { objClone[key] = obj[key]; } } } } return objClone; }
2、通过 JSON 对象实现深拷贝
//通过js的内置对象JSON来进行数组对象的深拷贝 function deepClone2(obj) { var _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone; }
问题:无法实现对对象中方法的深拷贝
3、通过jQuery的extend方法实现深拷贝
var array = [1,2,3,4]; var newArray = $.extend(true,[],array);
4、Object.assign()拷贝
当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝
5、lodash函数库实现深拷贝
lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
三、问题:包含处理循环引用问题的深拷贝
循环引用问题的产生原因可能是对象之间相互引用,也可能是对象引用了其自身,而造成死循环的原因则是我们在进行深拷贝时并没有将这种引用情况考虑进去,因此解决问题的关键也就是可以将这些引用存储起来并在发现引用时返回被引用过的对象,从而结束递归的调用。
/** * js深拷贝(包括 循环引用 的情况) * * @param {*} originObj * @param {*} [map=new WeakMap()] 使用hash表记录所有的对象的引用关系,初始化为空 * @returns */ function deepClone( originObj, map = new WeakMap() ) { if(!originObj || typeof originObj !== 'object') return originObj; //空或者非对象则返回本身 //如果这个对象已经被记录则直接返回 if( map.get(originObj) ) { return map.get(originObj); } //这个对象还没有被记录,将其引用记录在map中,进行拷贝 let result = Array.isArray(originObj) ? [] : {}; //拷贝结果 map.set(originObj, result); //记录引用关系 let keys = Object.keys(originObj); //originObj的全部key集合 //拷贝 for(let i =0,len=keys.length; i<len; i++) { let key = keys[i]; let temp = originObj[key]; result[key] = deepClone(temp, map); } return result; }
例子:
/**例子1: 数组深拷贝 */ let a = [1, 2]; let b = [4, 5, 6, a]; a.push(b); let c = deepClone(a); a.push(200); b.push(10); console.log(a); //[ 1, 2, [ 4, 5, 6, [Circular], 10 ], 200 ] console.log(b); //[ 4, 5, 6, [ 1, 2, [Circular], 200 ], 10 ] console.log(c); //[ 1, 2, [ 4, 5, 6, [Circular] ] ] /**a 和 b均变化,因为a、b互为直接引用, 而通过深拷贝,c不受a、b变化的影响 */ c[2][0] = 100; console.log(a); //[ 1, 2, [ 4, 5, 6, [Circular], 10 ], 200 ] console.log(b); //[ 4, 5, 6, [ 1, 2, [Circular], 200 ], 10 ] console.log(c); //[ 1, 2, [ 100, 5, 6, [Circular] ] ] /**深拷贝,c变化,而a和b未发生变化 */
参考:https://blog.csdn.net/chentony123/article/details/81428803
参考:https://blog.csdn.net/Snoopyqiuer/article/details/101106303