10分钟了解深浅拷贝
2024-12-09 11:13:16

对于深拷贝和浅拷贝,之前一直不怎么用到,所以一直也没太深入了解,22年的时候,被一个赋值问题教育了,这是那时候记录下的问题。

正文

面试回答

变量的每次声明都会开辟新的存储空间,js中为了找到对应的存储空间,需要指针来指向这个内容。

  1. 深拷贝,重新开辟一个存储空间,来存储你拷贝过来的内容
  2. 浅拷贝,简单的改一下引用指向,使新申明的内容指向原有的内容
  3. 深浅拷贝只针对对象和数组,无所谓好坏,看情况使用,一般是针对多层级的对象嵌套使用深拷贝,便于内容的修改
  4. 虽然有很多方法,但是个人一般使用自己写的js工具方法来处理这些小问题。

定义

在了解深浅拷贝之前,要先了解定义变量发生了什么。

我们每次申明变量,会在存储空间内分出来一块单位用来存储变量,而每个存储的变量,又会有个叫做指针的地址。

所以,我们在给其他变量赋值的时候,就会有个区分:是连着存储单位一块赋值的深拷贝,还是只给一个地址的浅拷贝。

深拷贝:开辟一块新的存储空间,存放原有的变量,并新开一个指针地址

浅拷贝:只拷贝指针地址,不另开辟存储空间

如果使用浅拷贝赋值,原有值会被修改

如果使用深拷贝赋值,原有值不会被修改

当然,有人可能会说我胡说,为什么string,boolean,null,undefined这些类型的值怎么没有改变原有值?

因为深复制和浅复制只针对象 Object对象、Array数组、RegExp对象(正则表达式)、Date时间对象、Function函数 这类复杂对象的。

简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

所以,一般深浅拷贝,都是针对数组或对象这类数据进行操作的。

理解

如果已经理解上边的朋友,可以跳过这段,这是给一些新手朋友理解的。

举个不恰当的例子,声明变量,就像我开一家公司,我必然会有个地址,也必然会在现实有个场地办公司。

我如果要开子公司,子公司就相当于我声明的新变量,而我要把母公司挂名给子公司,就像是赋值这个过程。

但是开的子公司,我可以只挂名,不开新办公室,直接把新公司的地址指向母公司————这就类似于浅拷贝;

那么,与之相对的,如果子公司不仅挂名,而且新开了一间办公室,那么我的新公司的地址就指向了新的办公室————这就深拷贝。

深拷贝会有个新空间来存储赋值后的变量,而浅拷贝没有。

所以,我们继续用这个例子来讲解:

如果是深拷贝,我要修改新公司的布局,因为新公司指向的是新办公室,所以只会修改新办公室的布局,而母公司的办公室不会被修改;

但若是浅拷贝,我要修改新公司的布局,因为新公司指向的是老办公室,所以,原有办公室的布局会被修改;

深拷贝应用

浅拷贝没什么好说的,而深拷贝则需要一些特殊的方法赋值,以下记录一些常用的深拷贝使用手法。

1. 手动复制

1
2
3
obj1 = {a:1,b:2}

obj2 = {a: obj1.a,b: obj.b}

2. 对象只有一层的话。可以用object.assign({},obj1)

Es6的object.assign()是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身

所以只能实现一层基本类型属性的拷贝 当obj1中属性是引用类型时,就会发现,修改其中一个属性值,另一个值也发生变化。 如下所示:

1
2
3
4
5
6
7
8
9
let obj1 = {a: 1, b: { c: 2}}

let obj2 = Object.assign({},obj1)

// 或者 obj2 = {...obj1}

obj2.b.c = 4

console.log(obj1) //{ a: 1, b: { c: 4 } }

3. 转成json再转换回来

用JSON.stringify转为字符串 再用JSON.parse把字符串再转为新的对象

obj2 = JSON.parse(JSON.stringify(obj1))

坏处: 摒弃了对象的constructor,不管原来的constructor是什么,拷贝后都是object. 只能处理可以被json直接表示的数据结构,number,string,array,扁平对象;

boolean RegExp对象,无法通过此方式深拷贝

4. 递归拷贝

递归深拷贝的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
deepCopy(obj) {
if (!obj && typeof obj !== "object") {
throw new Error("error arguments");
}
// const targetObj = obj.constructor === Array ? [] : {};
const targetObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
// 只对对象自有属性进行拷贝
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
targetObj[key] = deepCopy(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}

缺点: 相互引用会出现死循环,深拷贝的做法是遇到对象就进行递归 复制,那么结果只能无限循环下去

5. object.create()方法

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

也就是说,现有对象是新的对象的构造函数的prototype.其实现过程如下:

1
2
3
4
5
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}

6. 直接使用一些库函数方法, 如lodash

1
2
var _ = require('lodash')
_.cloneDeep()

结语

深浅拷贝也算是经典面试题了,无论什么时候都会有人考。

参考链接

https://juejin.cn/post/6844904178934874126

https://juejin.cn/post/6871053262767734791