深拷贝与浅拷贝

深拷贝VS浅拷贝

  • 深拷贝和浅拷贝针对的是引用类型
  • JS中分为值类型和引用类型,当赋值类型是引用类型,实际是对地址的拷贝,两个变量指向的同一份数据
  • 对于其中一个进行修改,同时会影响到另外一个

浅拷贝

浅拷贝也就是拷贝原对象里面的数据,但是不拷贝原对象里面的子对象,也就是会克隆出一个对象,数据相同,但是子对象的引用地址不会被拷贝,子对象引用地址与原子对象相同。

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
36
37
38
39
40
const obj1 = {
name:'Bob',
car: {
brand: "Wuling",
color: "White"
},
getAge:function(){
console.log('24');
}
}

// 赋值
const obj2 = obj1;
obj2 // {name: "Bob", car: {…}, getAge: ƒ}
obj2 === obj1 // true 两个对象的引用地址依然为同一个

// 浅拷贝
function shallowClone(obj) {
const target = {};
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
return target;
}

obj3 = shallowClone(obj1); // 浅拷贝
obj3 // {name: "Bob", car: {…}, getAge: ƒ} 数据与原对象一致
obj3 === obj1; // false 两个对象的引用地址并非同一个
obj3.name = "Alice";
obj3 // {name: "Alice", car: {…}, getAge: ƒ}
obj1 // {name: "Bob", car: {…}, getAge: ƒ} 原对象的首层属性并未改变

obj3.car.color = "Red"
obj3.car // {brand: "Wuling", color: "Red"}
obj1.car // {brand: "Wuling", color: "Red"} // 原对象的子对象也跟着改变
obj2.car // {brand: "Wuling", color: "Red"} // 赋值过的对象的子对象也跟着改变


深拷贝

  • 深拷贝只能拷贝首层数据,更深层次的数据依然指向原来的地址
  • 常用的浅拷贝有两种方法
    • JSON.parse(JSON.stringify(Object))
    • 遍历对象属性

JSON.parse(JSON.stringify(Object))

该种方法中 undefined、function、symbol 会在转换过程中被忽略。

1
2
3
4
5
6
7
8
9
const obj1 = {
name: 'Bob',
getAge: function(){
console.log('24');
}
}
console.log(obj1); // {name: "Bob", getAge: ƒ}
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2); // {name: "Bob"} getAge 函数已经丢失

遍历对象属性

可以通过遍历的方法解决部分类型被忽略的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function deepClone(obj) {
const target = {};
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof key === 'object') {
deepClone(obj[key]);
} else {
target[key] = obj[key];
}
}
}
return target;
}

const obj3 = shallowClone(obj1);
console.log(obj3); // {name: "Bob", getAge: ƒ}
obj3 === obj1; // false
obj3.name = "Alice";
console.log(obj3); // {name: "Alice", getAge: ƒ}

上面的方法并没有对传入参数进行校验,若传入对值为 null,则应该直接返回null
同时要考虑对数组对支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepClone(obj) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}
const target = Array.isArray(obj) ? [] : {};
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof key === 'object' && obj !== null) {
deepClone(obj[key]);
} else {
target[key] = obj[key];
}
}
}
return target;
}

循环引用

使用哈希表

设置一个数组或者哈希表存储已经拷贝过对对象,当检测到对象已经存在哈利表中,取出该值即可。
使用到了,ES6 WeakMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function deepClone(obj, hash = new WeakMap()) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}
if(hash.has(obj)){
return hash.get(obj); // hash查询
}
const target = Array.isArray(obj) ? [] : {};
hash.set(obj, target); // 设置hash
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof key === 'object' && obj !== null) {
deepClone(obj[key], hash); // 传入hash
} else {
target[key] = obj[key];
}
}
}
return target;
}

使用数组

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
function deepClone(obj, uniqueList = []) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}

uniqueList ? "" : uniqueList = []; // 初始化数组

const target = Array.isArray(obj) ? [] : {};

// 查找数据,数据若已经存在,返回保存到数据
if( uniqueList.length > 0 ) {
for(var i = 0; i < uniqueList.length; i++) {
if (uniqueList[i].obj === obj) {
return uniqueList[i];
}
}
} else {
return null;
}


for(var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof key === 'object' && obj !== null) {
deepClone(obj[key], uniqueList); // 传入hash
} else {
target[key] = obj[key];
}
}
}
return target;
}

递归爆栈

顾名思义,防止程序无限递归,导致报错

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
36
37
38
39
function deepClone(obj, uniqueList = []) {
const root = {};

const loopList = [
{
parent: root,
key: undefined,
data: x
}
];

while(loopList.length) {
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;

let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}

for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}

return root;
}

参考

深拷贝的终极探索(99%的人都不知道)