0%

赋值、浅拷贝和深拷贝

什么是深拷贝和浅拷贝?

怎么实现一个深拷贝?

核心内容

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)

如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

赋值

根本js有两种数据类型,赋值也可分为两种:

  • 基本数据类型:赋值,赋值之后两个变量互不影响
  • 引用数据类型:赋值,两个变量有相同的引用,都指向同一个引用,相互之间有影响

🤔️ 那基本类型赋值不就是深拷贝吗?

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

🤔️ 那如果这个对象只有一层,而且都是基本数据,那对这个对象的浅拷贝其实就相当于深拷贝

浅拷贝使用场景
  1. Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
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
let a = {
name: "baby",
other: {
price: "45"
}
}
let b = Object.assign({}, a);
console.log(b);
// {
// name: "baby",
// other: {price: "45"}
// }

a.name = "chenmi";
a.other.price = "无价";
console.log(a);
// {
// name: "chenmi",
// other: {price: "无价"}
// }

console.log(b);
// {
// name: "baby",
// book: {title: price: "无价"}
// }

通过上面这个卖孩子的🌰 我们可以知道,拷贝的第一层里面的引用数据还是会受到原来数据的影响

🤔️ 如果区分深浅拷贝是根据:拷贝的数据是否和原来数据的相互影响,那上面的🌰,如果都是基本数据,那它就是深拷贝,如果包括引用数据类型,那它就是浅拷贝
如果区分是根据拷贝数据是否超过一层不受影响:那上面的🌰,就是浅拷贝,只有里面所有的层都不受影响才是深拷贝

也不知道上面我的理解对不对,网上两个版本都有

  1. 展开语法 Spread
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
let a = {
name: "baby",
other: {
price: "45"
}
}
let b = {...a};
console.log(b);
// {
// name: "baby",
// other: {price: "45"}
// }

a.name = "chenmi";
a.other.price = "无价";
console.log(a);
// {
// name: "chenmi",
// other: {price: "无价"}
// }

console.log(b);
// {
// name: "baby",
// book: {title: price: "无价"}
// }

这个和Object.assign()的结果一样

  1. Array.prototype.slice()
1
2
3
4
5
6
7
8
9
10
11
12
let a = [0, "1", [2, 3]];
let b = a.slice(1);
console.log(b);
// ["1", [2, 3]]

a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]

console.log(b);
// ["1", [4, 3]]

改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意。

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

深拷贝使用场景
1
JSON.parse(JSON.stringify(object))

这个不管是对象还是数组,都能实现深拷贝

但是该方法有以下几个问题。

  • 1、会忽略 undefined

  • 2、会忽略 symbol

  • 3、不能序列化函数

  • 4、不能解决循环引用的对象

  • 5、不能正确处理new Date()

  • 6、不能处理正则

undefined、symbol 和函数这三种情况,会直接忽略。

1
2
3
4
5
6
7
8
9
let obj = {
name: 'muyiy',
a: undefined,
b: Symbol('muyiy'),
c: function() {}
}
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy"}

循环引用情况下,会报错

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

new Date 情况下,转换结果不正确

1
2
3
4
5
6
7
8
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)

JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""

JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"

解决方法转成字符串或者时间戳就好了。

1
2
3
4
5
6
7
8
let date = (new Date()).valueOf();
// 1545620645915

JSON.stringify(date);
// "1545620673267"

JSON.parse(JSON.stringify(date));
// 1545620658688

正则情况下,

1
2
3
4
5
6
7
8
9
10
let obj = {
name: "muyiy",
a: /'123'/
}
console.log(obj);
// {name: "muyiy", a: /'123'/}

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy", a: {}}

PS:为什么会存在这些问题可以学习一下 JSON

除了上面介绍的深拷贝方法,常用的还有jQuery.extend() 和 lodash.cloneDeep()