最近在学习JavaScript,学习过程中发现了一个例程中使用了Object.assign来进行对象的拷贝。于是自己做了一个小测试,发现了如下的问题。
example 1:
var a = 0;
var b = a;
console.log(a); // => 0
console.log(b); // => 0
b = 1;
console.log(a); // => 0
console.log(b); // => 1
如上,第一次打印输出,a和b都为0,b赋值为1后再打印输出,a不变,b为1。
example 2:
var a = {x : 0};
var b = a;
console.log(a); // => { x: 0}
console.log(b); // => { x: 0}
b.x = 1;
console.log(a); // => { x: 1}
console.log(b); // => { x: 1}
如上,第一次打印输出,a和b都为{ x: 1},第二次打印输出,a和b都为{ x: 1}
至此,问题已经体现出来了:
当a和b都是数字时,b的改变不会影响到a;
当a和b都是对象时,b的改变造成了a的改变。
我们在进行这两个程序编程时,都会意识到以下两点:
- 我们将a拷贝给了b
- 我们改变了b
但是我们没考虑到的是,在example 2中,a受到了b的影响,这显然不是我们期望得到的结果。
而以上所提及的,就是深拷贝和浅拷贝的区别。以下,我们先对深拷贝和浅拷贝做一个理解。
浅拷贝Shallow Copy,是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝Deep Copy,会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
也就是说,深拷贝是开辟了新的内存空间来接受目标,而浅拷贝是把目标映射到了原来的内存上了。所以,当使用浅拷贝时,两个参数实际只是一处地址的两个别名,改变任意参数,实际改变的是对应地址内存中的数据,就会造成另一个同样指向这个地址的参数。
我发现在JavaScript、C++、java等语言中都会有深拷贝和浅拷贝的概念。在我看来究其原因就是为了所谓的安全操作而摒弃了指针这个概念。在有指针概念的语言,比如C语言中,会出现以下的赋值语句:
int a = 5;
int b = a;
int* p = &a;
不难看出的是:
第一条语句中,声明并定义了a,声明a为整型,开辟了内存空间,并将5赋值给a;
第二条语句中,声明并定义了b,声明b为整型,开辟了内存空间,并将a=5赋值给b;
第三条语句中,声明并定义了指针p,声明p为一个指针,开辟了内存空间,并将a的地址赋给了p。
这样,我们操作b时,实际上时和a没有任何关系的,因为本身就是两处内存。而进行诸如
p = 10的操作时,实际上是在操作和a所对应的同一处内存,这样a的值也就发生了改变。而类似javaScript语言在进行拷贝操作时,为了其性能,会把比较复杂的对象的拷贝默认为浅拷贝,也就是类似上述的第三条语句的操作,只是简单的做了一个地址的映射,这样就导致了文章开头example 2中出现的问题。
那么在javaScript中,如何进行对象的深拷贝?
第一种,最简单粗暴的方法,由于当进行数字这种简单的类型的拷贝使用的是深拷贝,那么我们就先给目标开辟一个空间,然后手动的将所有值都提取出来并一一对应:
var a = {x : 0, y : 0};
var b = {};
b.x = a.x;
b.y = a.y;
console.log(a); // => { x: 0, y : 0}
console.log(b); // => { x: 0, y : 0}
b.x = 1;
console.log(a); // => { x: 0, y : 0}
console.log(b); // => { x: 1, y : 0}
第二种,在ES6中提供了Object.assign,也就是开头提到的方法,来完成深拷贝:
var a = {x : 0, y : 0};
var b = Object.assign({}, a);
console.log(a); // => { x: 0, y : 0}
console.log(b); // => { x: 0, y : 0}
b.x = 1;
console.log(a); // => { x: 0, y : 0}
console.log(b); // => { x: 1, y : 0}
深拷贝是一个重要的也是比较难的部分,这里只是为了解释深拷贝和浅拷贝,没有对深拷贝实现做太多工作。有需要和兴趣的可以自己找找资料。