浅拷贝与深拷贝

存储位置

在JS中,对于简单数据类型,比如number、null、undefined、string、boolean来说

它们的值是存在栈中的

而对于复杂数据类型,比如Object(array,function,Date)等,它的具体内容是存放在堆中的,在栈中存放指向该引用类型的地址

赋值

知道了它存储的位置,对于赋值操作也就不难理解了

1
2
3
4
5
6
let arr = [1,2,3];
let arr2 = arr;
console.log(arr2); //[1,2,3]
arr2[1] = 9;
console.log(arr); //[1,2,3]
console.log(arr2); //[1,2,3]

正是由于它们存储的是同一个地址,当改变其中一个副本的值的时候,另一个值也会随之改变

浅拷贝

数组浅拷贝

数组浅拷贝有许多原生的方法

比如:slice方法、concat方法、Array.from()等

1
2
3
4
5
6
7
8
9
let arr = [1, [2, 3], 4];
let arr1 = arr.slice(0);
let arr2 = [].concat(arr);
let arr3 = Array.from(arr);
arr2.push(9);
arr2[1].push(6);
console.log(arr1); //[1,[2,3,6],4]
console.log(arr2); //[1,[2,3,6],4,9]
console.log(arr3); //[1,[2,3,6],4]

值得注意的是,ES6中的扩展运算符对于一维数组可以实现深拷贝,但是对于多维数组只能实现浅拷贝,对于对象同样如此

1
2
3
4
5
6
7
8
9
10
let arr = [1,2,3];
let arr2 = [...arr];
arr2.push(6);
console.log(arr2); //[1,2,3,6]
console.log(arr); //[1,2,3]
let arr3 = [1,[3,4],2];
let arr4 = [...arr3];
arr4[1][0] = 8;
console.log(arr3); //[1,[8,4],2]
console.log(arr4); //[1,[8,4],2]

对象浅拷贝

1
2
3
4
5
6
7
8
9
let obj = {
name:"zhangsan",
hobby:['杀人','放火']
}
let obj2 = Object.assign({},obj);
obj2.hobby[0] = '打游戏';
obj2.name = 'lisi';
console.log(obj);
console.log(obj2);

运行结果

手写浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function shallowCopy(obj){
let newobj = Array.isArray(obj)?[]:{}
for(let i in obj){
if(obj.hasOwnProperty(i)){
newobj[i] = obj[i]
}
}
return newobj
}
let obj = {
name:"zhangsan",
hobby:['杀人','放火']
}
let arr = [1,[2,3],4];
let newarr = shallowCopy(arr);
newarr[1][0] = 5;
console.log(arr);
console.log(newarr);
let obj3 = shallowCopy(obj);
obj3.hobby[1] = '睡觉'
console.log(obj);
console.log(obj3);

运行结果

深拷贝

JSON.parse(JSON.stringify())

这是最简单也是最常用的一种方法

缺点:undefined、function、symbol会在转换过程中被忽略

1
2
3
4
5
6
7
8
let obj = {
name:"zhangsan",
hobby:['杀人','放火']
}
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.hobby[0] = '学习';
console.log(obj);
console.log(obj2);

运行结果

手写深拷贝

用递归来实现,直到不属于复杂类型再返回

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
function deepClone(obj) {
let type = Object.prototype.toString.call(obj).slice(8, -1);
if (type === 'Object') {
let newobj = {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
newobj[i] = deepClone(obj[i])
}
}
return newobj;
}
if (type === 'Array') {
let newarr = [];
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
newarr[i] = deepClone(obj[i])
}
}
return newarr;
}
return obj;
}
let obj = {
name: "zhangsan",
hobby: ['杀人', '放火']
}
let obj2 = deepClone(obj);
let arr = [1, 2, 3, [4, 5, 6]]
let arr2 = deepClone(arr);
arr2[3][1] = 8;
obj2.hobby[0] = '学习';
console.log(arr);
console.log(arr2);
console.log(obj);
console.log(obj2);

运行结果

然而实际上仍然存在一些问题,比如循环引用导致的爆栈以及Symbol类型的处理等(看以后是否填坑吧)

第三方插件

比如lodash的深拷贝


浅拷贝与深拷贝
https://blog-theta-ten.vercel.app/2021/08/09/浅拷贝与深拷贝/
作者
Chen
发布于
2021年8月9日
许可协议