JavaScript的浅拷贝和深拷贝

JavaScript的浅拷贝深拷贝一直都是很常见的基础问题,一般相关的问题可以直接用jQuery、lodash这样的库一行解决,但是学会了用还是要懂它背后的基础原理。

特别用这一篇笔记整理一下JavaScript浅拷贝、深拷贝的知识。

什么是浅拷贝、深拷贝

浅拷贝、深拷贝是在数组和对象下才存在的概念。浅拷贝指只拷贝了数组或对象的引用,并没有完全拷贝整个对象为一个独立的新对象,而深拷贝则是构建一个和原有对象完全一样的新对象,且它相对于新对象是独立的。

下面来举个实际的例子。

1
2
3
4
5
6
7
var obj = {
a: {
d: 1
},
b: 2,
c: 3
}

如果是浅拷贝,那么代码可能会类似于这样:

1
2
3
4
5
6
7
function simpleCopy(obj) {
let new_obj = Array.isArray(obj) ? [] : {};
for (let i in obj) {
new_obj[i] = obj[i];
}
return new_obj;
}

我们实际用这个代码在V8下对上面定义的obj进行一个浅拷贝,并且对拷贝后得到的对象进行修改:

1
2
3
var obj_2 = simpleCopy(obj);
obj_2.a.d = 99;
console.log(obj);

这个时候我们得到的结果是obj的a.d被也被更改成了99,所以这里浅拷贝拷贝到的实际上是引用,obj_2和obj并不是独立的。

但是对于表层的数据,浅拷贝拷贝出来的并非是引用,比如接着执行下面的代码:

1
2
obj_2.b = 99;
console.log(obj);

obj.b是不会发生变化的,因为我们的代码是把obj.b的值赋给了obj_2.b,对于number这个类型,obj_2.b得到的是真实值,但是对于object(object包括数组),obj_2这边的得到就是一个引用,我们对引用内的对象做修改自然会同时修改到源对象。

如果要做深拷贝的话,我们在拷贝的方法里不能简单用“=”赋值。

深拷贝的方法

要实现深拷贝,最简单的方式是递归:

1
2
3
4
5
6
7
function deepCopy(obj) {
let new_obj = Array.isArray(obj) ? [] : {};
for (let i in obj) {
new_obj[i] = typeof obj[i] == 'object' ? deepCopy(obj[i]) : obj[i];
}
return new_obj;
}

这是一个相当简单的深拷贝,我们用这个方法来重新创造一个obj_2:

1
2
3
var obj_2 = deepCopy(obj);
obj_2.a.d = 99;
console.log(obj);

在深拷贝下,原先的obj就没有发生变化。这里的深拷贝是一个非常简单的深拷贝,更加好的深拷贝需要考虑到环引用、继承/原型链、symbol等很多问题,具体可参考lodash的实现。

参考

《内功修炼之lodash—— clone&cloneDeep(一定有你遗漏的js基础知识)》
lodash源码