Open felix-cao opened 5 years ago
浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy),对于像 Object, Array 以及 ES6 新增的 Set,Map 这样的聚合对象才有实际意义,不同的是浅拷贝只复制聚合对象的第一层属性,深拷贝可以对对象的所有属性进行递归复制
Shallow Copy
Deep Copy
Object
Array
ES6
Set
Map
JavaScript 变量的数据类型分为基本数据类型和引用数据类型:
JavaScript
String
Number
Boolean
null
undefined
基本类型是按值访问的,不会影响到其他数据
var name = 'Felix' var frontName = name name = 'Lucy' frontName
上面的代码,将 name 赋值给 frontName 后再对 name 的值进行改变,并不会影响 frontName 的值,因为他们是基本数据类型,不会影响到其他数据。
name
frontName
所以按值访问的基本数据类型是没有深浅拷贝之别。
而引用类型的值是按地址访问的,简单的赋值,实际上只是把地址复制了一遍,修改任意一个值会影响到另外一个,例如
var people = { name: 'Felix', age: 18 } var student = people; student.name = 'student' console.log(people.name) // student
上面的代码可以看到,people 对象赋值给了 student 对象,JavaScript引擎只是将 people对象 的地址赋值给了 student,他们指向同一个内存地址,并没有开辟新的栈,当修改 people 或 student 对象中属性的值时,另外一个也被影响了,这就是浅拷贝!但是很多时候我们并不希望这样。
people
student
浅拷贝是对对象地址的拷贝,并没有开辟新的栈,复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,另外一个对象的属性也会改变。
浅拷贝的实现
聚合对象变量的直接赋值是一种浅拷贝。如下:
var m = { a: 10, b: 20 } var n = m; n.a = 15; console.log(m.a) // 15
上面的代码直接通过赋值的方式,是一种常见的浅拷贝
Array.concat(array) 方法会返回一个新的数组
Array.concat(array)
var arr1 = ['a', 'b', 'c']; var arr2 = ['d', 'e', 'f']; var coll = arr1.concat(arr2); arr1[0] = '被修改了'; console.log(arr1); // ["被修改了", 2, 3] console.log(coll) // [1, 2, 3, 4, 5, 6]
上面的代码,修改数组 arr1 并不会影响合并后的数组 coll 的值,这种表面现象误导了我很常一段时间,让我以为 concat 方法是深拷贝,后来阅读了MDN文档
arr1
coll
concat
concat 方法不会改变 this 或任何作为参数提供的数组,而是返回一个浅拷贝,它包含与原始数组相结合的相同元素的副本。 原始数组的元素将复制到新数组中,如下所示:
this
对象引用(而不是实际对象):concat将对象引用复制到新数组中。 原始数组和新数组都引用相同的对象。 也就是说,如果引用的对象被修改,则更改对于新数组和原始数组都是可见的。 这包括也是数组的数组参数的元素。
数据类型如字符串,数字和布尔(不是String,Number 和 Boolean 对象):concat将字符串和数字的值复制到新数组中。
简单的说,Array.concat(array) 方法是浅拷贝,但数组的元素为基本数据类型时会造成深拷贝的假象。
var arr1 = [{name: '小红'}]; var arr2 = [{name: '小明'}]; var coll = arr1.concat(arr2); console.log(coll); // [{name:'小红'},{name:'小明'}] arr1[0].name = '被修改了'; console.log(coll); // [{name:'被修改了'},{name:'小明'}]
上面的代码可以看出修改 arr1 下标0对应的对象,coll 的值也被影响了。他们指向同一个地址,所以concat 是浅拷贝,另外,Array.slice 方法和 concat 一样不会影响原数组,但同样是浅拷贝。
Array.slice
Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign()
Object.assign(target, ...sources)
他同样会让人产生困扰,如果对象深度只有1层,Object.assign() 产生的对象和 concat 一样,不会影响其他对象,例如
var obj1 = { a: 1, b: 2 }; var obj2 = { A: 1, B: 2 }; var coll = Object.assign({}, obj1, obj2); obj1.a = '被修改了'; console.log(coll); // {a: 1, b: 2, A: 1, B: 2}
修改 obj1.a 的属性不会影响 coll,是因为 obj.a 对应的 数字 1 是基本类型,按值访问。如果obj1.a 对应引用类型的值,复制的就是这个引用类型的地址。
obj1.a
obj.a
1
var obj1 = { a: { name: '小红' }, b: 2 }; var obj2 = { A: 1, B: 2 }; var coll = Object.assign({}, obj1, obj2); obj1.a.name = '被修改了'; console.log(coll); // {"a":{"name":"被修改了"},"b":2,"A":1,"B":2}
深拷贝可以通过JSON序列号后再解析回来的方式实现 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一来一回之间,新的对象产生了,而且对象会开辟新的栈,实现深拷贝
var obj1 = { a: { name: '小红' }, b: 2 }; var obj2 = JSON.parse(JSON.stringify(obj1)); obj1.a.name = '被修改了'; obj2 //{"a":{"name":"小红"},"b":2} 《---没有被修改
深拷贝在实际的业务中是最常见的应用场景,比如从服务器fetch到数据之后我将其存放在store中,通过props传递给界面,然后我需要对这堆数据进行修改,那涉及到修改就一定有保存和取消,所以我们需要将这堆数据拷贝到其他地方。
浅拷贝 (
Shallow Copy
) 与深拷贝 (Deep Copy
),对于像Object
,Array
以及ES6
新增的Set
,Map
这样的聚合对象才有实际意义,不同的是浅拷贝只复制聚合对象的第一层属性,深拷贝可以对对象的所有属性进行递归复制一、JavaScript 的两种变量数据类型
JavaScript
变量的数据类型分为基本数据类型和引用数据类型:String
,Number
,Boolean
,null
,undefined
;基本类型是按值访问的,不会影响到其他数据
上面的代码,将
name
赋值给frontName
后再对name
的值进行改变,并不会影响frontName
的值,因为他们是基本数据类型,不会影响到其他数据。所以按值访问的基本数据类型是没有深浅拷贝之别。
而引用类型的值是按地址访问的,简单的赋值,实际上只是把地址复制了一遍,修改任意一个值会影响到另外一个,例如
上面的代码可以看到,
people
对象赋值给了student
对象,JavaScript
引擎只是将people
对象 的地址赋值给了student
,他们指向同一个内存地址,并没有开辟新的栈,当修改people
或student
对象中属性的值时,另外一个也被影响了,这就是浅拷贝!但是很多时候我们并不希望这样。二、浅拷贝 (shallow copy)
浅拷贝是对对象地址的拷贝,并没有开辟新的栈,复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,另外一个对象的属性也会改变。
浅拷贝的实现
2.1、直接赋值
聚合对象变量的直接赋值是一种浅拷贝。如下:
上面的代码直接通过赋值的方式,是一种常见的浅拷贝
2.2、数组的 Array.concat() 函数
Array.concat(array)
方法会返回一个新的数组上面的代码,修改数组
arr1
并不会影响合并后的数组coll
的值,这种表面现象误导了我很常一段时间,让我以为concat
方法是深拷贝,后来阅读了MDN文档concat
方法不会改变this
或任何作为参数提供的数组,而是返回一个浅拷贝,它包含与原始数组相结合的相同元素的副本。 原始数组的元素将复制到新数组中,如下所示:对象引用(而不是实际对象):
concat
将对象引用复制到新数组中。 原始数组和新数组都引用相同的对象。 也就是说,如果引用的对象被修改,则更改对于新数组和原始数组都是可见的。 这包括也是数组的数组参数的元素。数据类型如字符串,数字和布尔(不是
String
,Number
和Boolean
对象):concat
将字符串和数字的值复制到新数组中。简单的说,
Array.concat(array)
方法是浅拷贝,但数组的元素为基本数据类型时会造成深拷贝的假象。上面的代码可以看出修改
arr1
下标0对应的对象,coll
的值也被影响了。他们指向同一个地址,所以concat
是浅拷贝,另外,Array.slice
方法和concat
一样不会影响原数组,但同样是浅拷贝。2.3、对象的浅拷贝
Object.assign()
方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。Object.assign(target, ...sources)
他同样会让人产生困扰,如果对象深度只有1层,
Object.assign()
产生的对象和concat
一样,不会影响其他对象,例如修改
obj1.a
的属性不会影响coll
,是因为obj.a
对应的 数字1
是基本类型,按值访问。如果obj1.a
对应引用类型的值,复制的就是这个引用类型的地址。三、深拷贝 (deep copy) 的实现
深拷贝可以通过JSON序列号后再解析回来的方式实现 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一来一回之间,新的对象产生了,而且对象会开辟新的栈,实现深拷贝
四、应用场景
深拷贝在实际的业务中是最常见的应用场景,比如从服务器fetch到数据之后我将其存放在store中,通过props传递给界面,然后我需要对这堆数据进行修改,那涉及到修改就一定有保存和取消,所以我们需要将这堆数据拷贝到其他地方。