值和引用
简单值(即标量基本类型值)总是通过值复制的方式来赋值/传递,包括null、undefined、字符串、数字、布尔和ES6中的symbol。
复合值–对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。
var a = 2;
var b = a; // b是a的值得一个复本
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用
d.push(4);
c; // [1,2,3,4]
d; // [1,2,3,4]
变量a是一个标量基本类型值,持有该值得一个复本,b持有它的另一个复本。b更改时,a的值保持不变。
c和d则分别指向同一个复合值[1,2,3]的两个不同引用。请注意,c和d仅仅是指向值[1,2,3],并非持有。所以它们更改的是同一个值,随后它们都指向更改后的新值[1,2,3,4]。
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。如下例,b=[4,5,6]并不影响a指向值[1,2,3],除非b不是指向数组的引用,而是指向a的指针,但在js中不存在这样的困惑:
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
浅拷贝和深拷贝
在js中,对于Object和Array这类引用类型值,当从一个变量向另一个变量复制引用类型值时,这个值的副本其实是一个指针,两个变量指向同一个堆对象,改变其中一个变量,另一个也会受到影响。这种拷贝分为两种情况:拷贝引用和拷贝实例,也就是我们说的浅拷贝和深拷贝。
浅拷贝
拷贝原对象的引用,这是最简单的浅拷贝。除了上述的方法,还有:
1.Object.assign()
Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var x = {
a: 1,
b: { f: {g: 1} },
c: [1,2,3]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f); // true
深拷贝
深拷贝也就是拷贝一个新的实例,新的实例和之前的实例互不影响。主要方法有:
1.Array的slice和concat方法
Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。原数组的元素会按照下述规则拷贝:
- 如果该元素是个对象引用(不是实际的对象),slice会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
- 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
var o1 = ['darko', {age: 22}];
var o2 = o1.slice(); // 根据Array.prototype.slice()的特性,这里会返回一个o1的浅拷贝对象
console.log(o1 === o2); // => false,说明o2拷贝的是o1的一个实例
o2[0] = 'lin';
console.log(o1[0]); // => "darko" o1和o2内部包含的基本类型值,复制的是其实例,不会相互影响
o2[1].age = 23;
console.log(o1[1].age); // =>23 o1和o2内部包含的引用类型值,复制的是其引用,会相互影响
2.JSON对象的parse和stringfy
JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringfy方法可以将JS对象序列化成JSON字符串,借助这个方法,也可以实现对象的深拷贝。
var source = { name: 'source', child: { name: 'child' } };
var target = JSON.parse(JSON.stringfy(source));
target.name = 'target'; // 改变target的name属性
console.log(source.name); // source
console.log(target.name); // target
target.child.name = 'target child'; // 改变target的child
console.log(source.child.name); // child
console.log(target.child.name); // target child