浅析解构赋值到底是深拷还是浅拷
2024-12-09 11:13:16

群友:听说你最近在刷面试题?要不我问一道题你看看你能不能回答?

我:没问题,我对面试八股文多少还是有点自信的。

群友:那,解构赋值到底是深拷贝还是浅拷贝?

我:你他妈是故意来找茬的吧?

正文

从来没想过的基础问题,一下子被干懵了,我个人在入行第二年的时候就通读了《ES6标准入门》这本书。

关于解构赋值这里,因为用的不熟悉所以特别多看了两眼,我记得文中从来没提过这事儿,而且我也从来没想过这茬子事情。

哎,这种题作为面试题,真把人干躺了也无所谓,就当是吃个经验教训。

深浅拷贝

明确一下深拷贝和浅拷贝的定义,或者说深拷贝和浅拷贝所应用于的数据类型。

  • 深拷贝:修改新变量的值不会影响原有变量的值。默认情况下基本数据类型(number,string,null,undefined,boolean)都是深拷贝。
  • 浅拷贝:修改新变量的值会影响原有的变量的值。默认情况下引用类型(object)都是浅拷贝。

其实你只要理解透彻了这两句话就应该明白了解构赋值,甚至深拷贝的原理;

写两个例子,理解一下深拷贝和浅拷贝;

基本数据类型

我们平时开发中,这种情况很常见的,直接用等号赋值就是深拷贝,如果没有新的内存空间存放变量,我们岂不是会直接修改元数据?

因此,此时对b的数值的修改并未影响a,所以基本数据类型赋值就是深拷贝。

1
2
3
4
5
let a = 1;
let b = a;
b = 2;
console.log(a,b);
// 打印出:1,2

引用数据类型

所谓深浅拷贝,核心就是要不要用新的存储空间来存放变量,而且平时也主要是针对引用类型的数据。

这里直接用等号赋值,我们会发下,是浅拷贝赋值。

1
2
3
4
5
6
7
8
let a = {
name: 'xiaoming'
};
let b = a;
b.name = 'zhangsan';

console.log(a)
// 打印出:{name: '张三'}

其实这个时候,我们大概就能想明白一件事儿,那就是一门语言在面对这种大小不确定的引用类数据类型,底层原理上肯定是倾向于浅拷贝。

不然,各个都是深拷贝,那就得开辟大量的新空间用来存储,一旦全面铺开,将会有一笔很大的存储空间消耗。

如果是这样,那这门语言一定会得到最消耗内存的烂名声,任何一名语言设计者在设计之初的时候,肯定不会希望自己设计的语言大量消耗存储空间。

所以,大多语言,引用类型数据的默认赋值方式,大概率都会是浅拷贝。

解构赋值

在上边赋值的时候,我们已经注意到了,

ES6的解构赋值,大家应该都清楚,本质上其实就是一种语法糖,可以快速取出数组或者对象中的值,这里放个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
let a = {
name: 'name',
age: 18,
marriage: false,
}

let { name, age, marriage} = a;

name = 'name1';
age = 20;
marriage = true;
console.log(a)
// 打印出:{ name:'name',age:18, marriage: false }

发现a的数据并没有被改变,解构赋值好像是深拷贝啊?????

我们再改一下上面的例子看看:

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
const a = {
name: 'name',
age: 18,
marriage: false,
addr: { province: 'sichuan', city: 'chengdu' }
}

let { name, age, marriage, addr } = a

name = 'myname'
age = 26
marriage = true
addr.province = 'shanghai'
addr.city = 'shanghai'

console.log(name, age, marriage, addr)
console.log(a)

// 这里我直接将结果放在这里,感兴趣的可以直接用上述代码运行
// 打印出:myname 26 true {province: 'shanghai', city: 'shanghai'}
// 打印出:
{
addr: {province: 'shanghai', city: 'shanghai'},
age: 18,
marriage: false,
name: "name"
}

发现解构赋值出来的对象将原对象a中的addr的数据修改了,这样看还是浅拷贝;

这里我们根据上述情况,可以简单的

解构赋值,如果所解构的原对象是一维数组或对象,其本质就是对基本数据类型进行等号赋值,那它就是深拷贝;

如果是多维数组或对象,其本质就是对引用类型数据进项等号赋值,那它就是浅拷贝;

最终的结论就是:解构赋值是浅拷贝(因为它确实不能对多维数组或对象达到深拷贝的作用);

深拷贝本质

这里我们放一套常规的深拷贝赋值方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{
// 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}

你发现了吗?其实深拷贝本质上还是将对象拆开为基本数据类型进行赋值。

所以,解决问题的本质就是让复杂问题简单化,所谓的深拷贝,本质上无非就是加强版的基础类型赋值罢了。

1
2
// 如果不是,就直接赋值
targetObj[keys] = source[keys];

结语

虽然是非常基础的一个问题,但是却让我颇有受益。

所有语言开发之初,肯定对相关问题都有过优化,如今项目工程越来越繁杂,这种曾经精妙的设计确实也落入了尘埃,到底也是时代的眼泪了。

当然,感叹归感叹,但依然是实际开发中用不到的知识,不过确实有趣。

参考

ES6的解构赋值与深拷贝和浅拷贝 - 前端随笔 - 博客园 (cnblogs.com)