logo头像

流莹离|拼命往前,仗剑天涯

深拷贝和浅拷贝

深拷贝和浅拷贝的区别

  • 浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用

  • 深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

为什么要使用深拷贝?

我们希望在改变新的数组(对象)的时候,不改变原数组(对象)

深拷贝的要求程度

我们在使用深拷贝的时候,一定要弄清楚我们对深拷贝的要求程度:是仅“深”拷贝第一层级的对象属性或数组元素,还是递归拷贝所有层级的对象属性和数组元素?

怎么检验深拷贝成功

改变任意一个新对象/数组中的属性/元素, 都不改变原对象/数组

拷贝类型

1.基本数据类型的拷贝(名值存储栈内存中)

number,string,boolean,null,undefined,symbol

2.引用(复杂)数据(名存栈内存,栈内存有指向堆内存的地址,值存对内存)

对象{a:1},数组[1,2,3],函数

对象:日期对象,正则对象……

浅拷贝Object.assign

ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标,剩下的参数是拷贝的源对象;

语法:Object.assign(target, …sources)

Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是如果属性的值是对象的话,只会拷贝一份相同的内存地址。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var p = {
'name': '张三',
'user':{
age:'16'
}
};
var copyP = {};
Object.assign(copyP, p);
copyP.name = 'yang'
console.log(copyP.name);//yang
console.log(p.name);// 张三
copyP.user.age = '19'
console.log(copyP.user.age);//19
console.log(p.user.age);//19

浅拷贝扩展运算符

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {'name': '撩课', 'college': ['H5','JAVA','Python']}
var obj2 = {...obj};
obj.name='小撩';
//{'name': '小撩', 'college': ['H5','JAVA','Python']}
console.log(obj);
//{'name': '撩课', 'college': ['H5','JAVA','Python']}
console.log(obj2);
obj.college.push('Go');
//{'name': '小撩', 'college': ['H5','JAVA','Python','Go']}
console.log(obj);
//{'name': '小撩', 'college': ['H5','JAVA','Python','Go']}
console.log(obj2);

扩展运算符和Object.assign()存在同样的问题,对于值是对象的属性无法完全拷贝成两个不同对象;

但是如果属性都是基本类型的值的话,使用扩展运算符更加简洁。

深拷贝JSON.stringify

注意:

  • 拷贝的对象的值中如果有函数,undefined,symbol,经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失;

  • 拷贝的数组的值中如果有函数,undefined,symbol,经过JSON.stringify()序列化后的JSON字符串中,这个值会变成null

  • 无法拷贝不可枚举的属性,无法拷贝对象的原型链

  • 拷贝Date引用类型会变成字符串

  • 拷贝RegExp引用类型会变成空对象

对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = [
{
number1:null,
number2:undefined,
number3:function(){return 1},
number: 1,
second: {
name: '333'
}
},
function(){return 2},
[3, 4],
false,
true,
45,
null,
undefined,
{data:new Date()},
{reg:/\w/g},
Number('sdgd34')
]

console.log(JSON.stringify(obj))
// [{"number1":null,"number":1,"second":{"name":"333"}},null,[3,4],false,true,45,null,null,{"data":"2019-10-28T05:47:23.064Z"},{"reg":{}},null]

手写简单的拷贝对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 辅助函数, 判定是否是对象
* @param obj
* @returns {boolean}
*/
function isObj(obj) {
return obj instanceof Object;
}

/**
* 深拷贝fromObj面的所有属性/值, 到toObj对象里面
* @param fromObj 拷贝对象
* @param toObj 目标对象
*/
function deepCopyObj2NewObj(fromObj, toObj) {
for (var key in fromObj) {
if(fromObj.hasOwnProperty(key)){
var fromValue = fromObj[key];
// 如果是值类型,那么就直接拷贝赋值
if (!isObj(fromValue)) {
toObj[key] = fromValue;
} else {
// 如果是引用类型,那么就再调用一次这个方法,
// 去内部拷贝这个对象的所有属性
// fromValue是什么类型, 创建一个该类型的空对象
var tmpObj = new fromValue.constructor;

// console.log(tmpObj);
// debugger;
deepCopyObj2NewObj(fromValue, tmpObj);
toObj[key] = tmpObj;
}
}
}
}

存在大量深拷贝需求的代码——immutable提供的解决方案

实际上,即使我们知道了如何在各种情况下进行深拷贝,我们也仍然面临一些问题: 深拷贝实际上是很消耗性能的。(我们可能只是希望改变新数组里的其中一个元素的时候不影响原数组,但却被迫要把整个原数组都拷贝一遍,这不是一种浪费吗?)所以,当你的项目里有大量深拷贝需求的时候,性能就可能形成了一个制约的瓶颈了。

immutable的作用:

通过immutable引入的一套API,实现:

  • 在改变新的数组(对象)的时候,不改变原数组(对象)

  • 在大量深拷贝操作中显著地减少性能消耗

1
2
3
4
5
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50

总结

1)在日常开发中一般并不需要拷贝很多特殊的引用类型,深拷贝对象使用JSON.stringify是最直接和简单的方法。

2)实现一个完整的深拷贝是非常复杂的,需要考虑到很多边界情况。对于特殊的引用类型有拷贝需求的话,建议借助第三方完整的库。