深、浅拷贝由来
// 正常情况,a 值的改变不影响 b let a = 1 let b = a a = 2 console.log(b) // 1 // 不正常,a 中元素的改变影响了 b let a = [1, 2, 3] let b = a // 引用 a[0] = 4 console.log(b[0]) // 4 // 不正常 let a = {name: 'sam'} let b = a a.name = 'jack' console.log(b.name) // 'jack'
说明
基本数据类型:数据存储在栈中。
引用数据类型:数据存放在堆内存中,栈中存放了一个引用地址,指向堆内存中的数据。
引用赋值 引用数据赋值,是在栈中复制了引用地址,指向同一个堆内存中的数据,因此发生上述情况。
浅拷贝 会在堆内存中开辟一个新的空间,存储新的对象,拷贝变量存储的是新对象的引用地址。但是只是开辟一个新空间,如果原对象有多层嵌套,且子属性中有引用数据类型,那么子属性只是简单的复制地址空间,并不会再开辟新的堆内存空间。
深拷贝 不仅会开辟一个新的空间来存储新的对象,并且子属性中的所有引用数据类型都会重新开辟新的内存空间,新对象与原对象完全独立,互不影响。
浅拷贝
// 浅拷贝的实现 function clone(origin) { var target = {} for (var k in origin) { if (origin.hasOwnProperty(k)) { target[k] = origin[k] } } return target }
// js 提供的常见的方法都是浅拷贝 Object.assign() Array.prototype.concat() Array.prototype.slice()
深拷贝
// ES5 function deepClone(origin) { var target = {} var toStr = Object.prototype.toString var arrType = '[object Array]' for (var k in origin) { if (origin.hasOwnProperty(k)) { if (typeof origin[k] === 'object' && origin[k] !== null) { target[k] = toStr.call(origin[k]) === arrType ? [] : {} deepClone(origin[k], target[k]) } else { target[k] = origin[k] } } } return target } // ES6 function deepClone(origin) { if (origin == undefined || typeof origin !== 'object') { return origin } const target = new origin.constructor() for (let k in origin) { if (origin.hasOwnProperty(k)) { target[k] = deepClone(origin[k]) } } return target } // 利用 JSON 转换 var target = JSON.parse(JSON.stringify(origin)) // 这种方法的缺点是,对象属性中如果有函数类型的话就无法转换 // lodash 的 _.cloneDeep 函数 var target = _.cloneDeep(origin)