JavaScript中常见的深拷贝与浅拷贝方式
在开发环境中,对对象进行不同程度的拷贝是非常常见的操作,一般可以分为 深拷贝 和 浅拷贝。
本文所说的深浅拷贝只指不存在 function 属性的普通对象
深拷贝
深拷贝就是将对象中的每一个属性,无论是普通类型的数值属性,还是引用类型的指针,全部递归的进行拷贝,使得生成的新对象与源对象完全独立,即改变新对象的值,不会影响源对象的值。
对象之间完全独立的在内存中的表现就是对象的之间不存在共享内存。
假设有对象 objA:
var objA = {
name: 'A',
num: 1,
child: {
name: 'AA',
num: 2
},
list: [1, 2, 3]
}
对 objA
进行深拷贝后,得到的新对象(假设是 objB)将于原对象在内存中完全独立。
遍历赋值
将 objA
的每一个属性进行遍历,赋值到新的对象 objB
中,即可实现拷贝,使用循环递归遍历 objA
的每一个属性:
function deepCopy(obj, start) {
let objNew = start || {};
for (let key in obj) {
// 属性是引用类型,可能是 {} 或 []
if (typeof obj[key] === 'object') {
objNew[key] = obj[key].constructor === Array ? [] : {};
deepCopy(obj[key], objNew[key]);
}
else {
objNew[key] = obj[key];
}
}
return objNew;
}
测试深拷贝:
let objB = deepCopy(objA);
console.log(objA === objB); // false
console.log(objA.name === objB.name); // true
console.log(objA.child === objB.child); // false
console.log(objA.list === objB.list); // false
引用类型的全等判断全部返回 false ,这是符合深拷贝预期的。
JSON.parse方法
使用 JSON.parse 结合 JSON.stringify 进行深拷贝是最简单的,一般只需要一行代码即可完成。
还是以实现拷贝 objA 为例:
let objB = JSON.parse(JSON.stringify(objA));
测试深拷贝的结果:
console.log(objA === objB); // false
console.log(objA.name === objB.name); // true
console.log(objA.child === objB.child); // false
console.log(objA.list === objB.list); // false
可见于循环遍历拷贝的结果是一致的,这是一种最快捷的深拷贝。
不过该方法存在一定局限性,比如遇到某个属性值为 undefined
的时候则不可用。
浅拷贝
浅拷贝一般是将某个对象的特定层进行一次拷贝,以便实现属性引用和变量共享,常见的就是对数组和对象的拷贝。浅拷贝也可以使用单层遍历的方式实现,但使用 Object.create 会更方便。
Object.create()
使用 Object.create(obj) 实现的是对对象的浅拷贝,一行代码即可搞定。
假设 objB
是 objA
的浅拷贝:
let objB = Object.create(objA);
测试浅拷贝:
console.log(objA === objB); // false
console.log(objA.name === objB.name); // true
console.log(objA.child === objB.child); // true
console.log(objA.list === objB.list); // true
可以使用浅拷贝是,只有 objB
是独立对象,其引用类型的全部来自 objA
,且与 objA
共享地址,所以后面三相测试均得到 true 。
ES6 展开运算符
如果使用ES6语法,可以直接通过展开运算符进行单层拷贝,这种拷贝也属于浅拷贝,也是比较常用的一种方法。
实例如下:
let objB = {...objA};
这种方式的浅拷贝与使用 Object.create()
效果相同。