JS赋值、浅拷贝与深拷贝

赋值

b = a

赋值 = 是将某一数值或对象赋给某个变量的过程,分为下面 2 部分

  • 基本数据类型:赋值,赋值之后两个变量互不影响
  • 引用数据类型:赋,两个变量具有相同的引用,指向同一个对象,相互之间有影响
1
2
3
4
5
6
7
8
9
10
11
let c = 1;
let d = c;
d ++;
console.log(d); // 2
console.log(c); // 1

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

因为基本类型值不会随之改变,因此下面的浅拷贝和深拷贝是针对对象类型的。

浅拷贝

b = a.slice()

浅拷贝即创建一个新对象,改变新对象的基本类型值,原对象不会随之改变,但如果改变新对象中引用类型内的值,原对象也会随之改变。Object.assign() 就是浅拷贝,用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let a = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let b = Object.assign({}, a);
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// name: "change",
// book: {title: "You Don't Know JS", price: "55"}
// }
console.log(a);
// {
// name: "muyiy",
// book: {title: "You Don't Know JS", price: "55"}
// }

同理,对于对象类型下的数组,slice()concat()...运算符 经过测试后发现都是浅拷贝

1
2
3
4
5
6
let arr1 = [1 , 2 , 3 , {"name" : "张一"} , {"sex" : "male"}];
let arr2 = arr1.slice();
arr2[0] = 2;
arr2[3].name = "张三";
console.table(arr1) // 1 2 3 { name: "张三" } { sex: "male" }
console.table(arr2) // 2 2 3 { name: "张三" } { sex: "male" }
1
2
3
4
5
let arr2 = arr1.slice();
let arr2 = [].concat(arr1);
let arr2 = Array.from(arr1);
let arr2 = Array.of(...arr1);
let arr2 = [...arr1];

这里推荐使用扩展运算符…来进行浅拷贝({}、[]都推荐…),而不是Object.assign,详情请看下面的代码

Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

深拷贝

b = cloneDeep(a)

深拷贝即完全改变变量 b 之后对 a 没有任何影响。深拷贝相比于浅拷贝速度较慢并且花销较大。

JSON.parse+JSON.stringify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let a = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let b = JSON.parse(JSON.stringify(a));
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// name: "change",
// book: {title: "You Don't Know JS", price: "55"}
// }
console.log(a);
// {
// name: "muyiy",
// book: {title: "You Don't Know JS", price: "45"}
// }
1
2
3
4
5
6
let arr1 = [1 , 2 , 3 , {"name" : "张一"} , {"sex" : "male"}];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0] = 2;
arr2[3].name = "张三";
console.table(arr1) // 1 2 3 { name: "张一" } { sex: "male" }
console.table(arr2) // 2 2 3 { name: "张三" } { sex: "male" }

这个方法很简便,但是有以下几个问题。

1、会忽略 undefined

2、会忽略 symbol

3、不能序列化函数

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

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

6、不能处理正则

DFS

浅拷贝 + 递归

这里呢只实现最简单的深拷贝,只考虑 数组{} 的情况,并且没有循环引用

1
2
3
4
typeof null //"object"
typeof {} //"object"
typeof [] //"object"
typeof function foo(){} //"function" (特殊情况)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function isObject(obj) {
return typeof obj === 'object' && obj != null;
}
// 对 null 做一下判断
function cloneDeep(source) {
if (!isObject(source)) return source;
// 非对象、null 或者 function 的话返回自身
let target = Array.isArray(source) ? [] : {};
// 直接使用Array.isArray方法进行判断:克隆 [] 或者 {}
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}

小结

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变会使原数据一同改变 改变会使原数据一同改变

参考

https://github.com/airbnb/javascript