JS 中的深拷贝与浅拷贝

前言 最近在写项目的时候涉及到一些父子组件传递个对象或者数组通信啥的,或者是直接复制添加对象啥的,直接使用赋值的时候总会出错

1 什么是深拷贝&浅拷贝

见名知义,无论是深拷贝还是浅拷贝,都是 copy 的问题。就是 copy 的时候出现的两种情况。区分起来也挺简单的,举个例子,假设 B 是 A 复制过来的,当我们修改 A 的时候,B 也随之改变了,那么这个就是浅拷贝,那要是 B 没有随 A 一起改变的话,那么这个就深拷贝了。

2 现实场景

首先呢,我们先要明白在 Javascript 中,有 5 种简单数据类型(也称为基本数据类型),分别是 Undefined,Null,String,Number,Boolean,还有 1 种复杂数据类型即 Object,(ES6 新出的 Symbol 数据类型就先不讨论了)

2.1 基本数据类型

对于基本数据类型的复制就谈不上什么深拷贝和浅拷贝了,对于基本数据类型来说,他们的值在栈内存中占据着固定大小的空间,并被保存在栈内存中。假设 变量 b 复制 基本数据类型变量 a,那么 b 会内存中占据自己的空间,和 a 就没啥关系了,大家各管各的,互不干涉。

1
2
3
4
5
let a = 2; 
let b = a;
b = 4;
console.log(a); // 2
console.log(b); // 4

2.2 复杂数据类型(Object)

对于对象的话,他是引用类型,复制起来就要区分浅拷贝和深拷贝了,因为 Object 是引用类型,他真正的值保存在堆内存中,他在栈内存存储是变量名和指向该对象值的指针(就是一个地址),如下图所示。
image
所以当我们用平常用一个变量去复制一个 Object 类型的变量的时候,复制的是他的指针地址而已,所以两个变量最终都指向同一个变量,大家要改一起改,这就是浅拷贝啦,如下

1
2
3
4
5
let obj1 = {name:'kk',age:12,desc:'源对象'}
let obj2 = obj1;
obj2.desc = '目标对象'
console.log(obj1); //{name:'kk',age:12,desc:'目标对象'} 此处源对象跟着一起变了
console.log(obj2); // {name:'kk',age:12,desc:'目标对象'}

啥,不信?!,那就看图
image
如何
image
但是在我们日常的使用当中,Object 类型的浅拷贝的行为会让我们很迷,我复制这个对象就是想复制他的值而已啦,不要复制人家个值就和他绑到一块了,跟他一起「同生共死」。所以啊,当我们想按照我们复制的想法,就只复制他的值用来自己用,他的是他的,我的是我的,大家井水不犯河水。接下来就要说咋办了。

3 实现对象类型的深拷贝

对于对象的深拷贝,搜集了网上的资料,就有下面三种方法

3.1 slice()&concat()

这个是针对数组的深拷贝,可以通过这两个方法实现对数组的深拷贝,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = [1,2,3,4]
let arr2 = arr.slice();
arr[0] = 0;
console.log(arr); //[1,2,3,4]
console.log(arr2); //[1,2,3,4]
```
`concat` 同理可得,不过这两个方法有个问题,`slice()` 和 `concat()` 方法能够深拷贝的就只有数组的一级属性,但是如果是多维数组的话,那么只有一级属性的值是深拷贝,往下就都是浅拷贝了,如下所示
```javascript
let arr = [[1,2],2,3,4]
let arr2 = arr.concat();
arr[0][0] = 0;
arr[1] = 1;

console.log(arr); //[[0,2],1,3,4]

console.log(arr2); //[[0,2],2,3,4]
// arr2[1] 没变,但是 arr2[0] 跟着一起改了

image

3.2 JSON 的骚操作

通过 JSON 的 stringify, parase 操作也可以实现对象的深拷贝。

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
let obj1 = {name:'kk',age:12,desc:'源对象'}
let obj2 = JSON.parase(JSON.stringify(obj1));
obj2.desc = '目标对象';
console.log(obj1); //{name:'kk',age:12,desc:'源对象'} 此处源对象就没有一起变了
console.log(obj2); // {name:'kk',age:12,desc:'目标对象'}
```
此法数组和对象都可以用。
### 3.3 自己写一个深拷贝函数
自己动手,丰衣足食
```javascript
function deepCopy(obj) {
let newObj = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//判断ojb子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
newObj[key] = deepCopy(obj[key]);
} else {
//如果不是,简单复制
newObj[key] = obj[key];
}
}
}
}
return newObj;
}

let a = [1,2,3];
let b = deepCopy(a);
a[0] = 0;
console.log(a); //[0,2,3]
console.log(a); // [1,2,3]

3.4 JQ 的 extend 方法

这个就直接放文档了
$.extend( [deep ], target, object1 [, objectN ] )
deep:如果设为true,则递归合并即深拷贝。
target:待修改对象。
object1:待合并到第一个对象的对象。
objectN:待合并到第一个对象的对象。
使用如下

let a = [1,2,3],
let b = $.extend(true,[],a);
a[0]=1;
console.log(a); // [0,2,3]
console.log(b); // [1,2,3]

以上就是关于 JS 中的深拷贝与浅拷贝的知识和如何进行深拷贝的知识了,如果有错或者有其他方式的话,欢迎在下面留言评论啦。



----------- 本文结束 -----------




小红帽 wechat
想看更多文章,那就订阅我的微信公众号吧
如果觉得我的文章对你有很大帮助的话,请我喝杯奶茶吧~(@^_^@)~