深拷贝和浅拷贝
# 前言
拷贝目标对象中基本类型的属性值,浅拷贝和深拷贝的处理都是一样的,都是简单的将目标对象的属性值拷贝一份到一个新对象中;但是对于引用类型的属性值,浅拷贝只拷贝引用地址,所以拷贝的对象和被拷贝的对象始终指向同一个内存地址,改变其中一个对象的属性值,另外一个对象的属性值也会被修改;深拷贝则是拷贝整个对象,内存中会单独开辟一个空间给拷贝的新对象,修改其中一个对象的值,对另外一个对象没有任何影响。
# 理解浅拷贝和深拷贝
复制对象属性只复制到第一层,就是浅拷贝;复制层级超过一层就是深拷贝
看这样一个例子:
const obj = {
name: '小新',
age: 5,
a: {b: {c: 1}}
}
const cloneObj = {}
for (let key in obj) {
cloneObj[key] = obj[key]
}
//第一级属性赋值 两个对象不会影响
cloneObj.a = 1
console.log(obj.a) // { b: { c: 1 } }
console.log(cloneObj.a) // 1
// 超过第一级属性赋值 两个对象相互影响
clonetObj.a.b = 1;
console.log(obj.a) // { b: 1 }
console.log(cloneObj.a) //{ b: 1 }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里的处理方式就是浅拷贝,为什么是浅拷贝呢?因为我们只拷贝了第一级的属性,对于基本类型,本来就只有一级;对于上面例子中的引用类型,我们只克隆了a
这个引用,a
中的b
引用指向的内存地址还是之前的对象中的引用b
指向的地址,所以当我们给a.b
重新赋值时,obj.a.b
和cloneObj.a.b
都会是我们新赋值的值。
# 浅拷贝的实现
# 1.粗暴遍历对象的每一个属性,将遍历到的属性放入创建的新对象中
const obj = {
name: '小新',
age: 5,
do: function () {
conosole.log("写作业!!")
}
}
const cloneObj = {}
for (let key in obj) {
cloneObj[key] = obj[key]
}
console.log(cloneObj) // { name: '小新', age: 5, do: [Function: do] }
2
3
4
5
6
7
8
9
10
11
12
# 2.使用object.assign(target, source)
const obj1={a:{b:1}};
const obj2=Object.assign({},obj1);
console.log(obj2); // { a: { b: 1 } }
obj1.a.b=2;
console.log(obj2.a.b) // 2 // 两个对象的值相互影响了
2
3
4
5
6
# 3.使用ES6的...
运算符
const obj1 = {a:{b:1}};
const obj2 = {...obj1}
console.log(obj2) // { a: { b: 1 } }
2
3
同理也只是处理了第一级的属性
引用类型还包括其他如数组、正则、日期等这里暂时不考虑
# 深拷贝实现
# 1.使用JSON.stringift和JSON.parse
JSON.stringify处理的逻辑是将引用类型转化成基本类型,然后将这个基本类型的引用进行赋值,达到克隆对象的结果
const obj1 = {a:{b:1}};
const obj2 = JSON.stringify(obj1);
console.log(typeof obj2); // string
console.log(typeof JSON.parse(obj2)) // object
2
3
4
5
知道原理后我们这样做:
const obj1 = {a:{b:1}};
const obj2 = JSON.stringify(obj1);
const cloneObj = JSON.parse(obj2)
// 修改克隆后的对象的属性
cloneObj.a.b = 10;
// 不会影响之前被克隆对象的b属性
console.log(obj1) // { a: { b: 1 } }
console.log(cloneObj); // { a: { b: 10 } }
2
3
4
5
6
7
8
9
10
11
但是,值得注意的是JSON.stringify
和JSON.parse
也有弊端:
# JSON.stringify
克隆时会忽略类型为undefined
、symbol
、function
的属性值。
const obj = {
a:1,
b:undefined,
c:function(){},
d: Symbol('我是一个快乐的symbol!')
}
const tempObj = JSON.stringify(obj);
const cloneObj = JSON.parse(tempObj);
console.log(cloneObj) // { a: 1 }
2
3
4
5
6
7
8
9
10
# 无法兼容循环引用
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj)); // 报错 TypeError: Converting circular structure to JSON
2
3
4
5
6
7
8
9
10
11
# 不能处理正则对象
// 不能处理正则对象
const obj1 = {
a:1,
b: /'hhh'/
}
console.log(JSON.parse(JSON.stringify(obj1))) // { a: 1, b: {} }
2
3
4
5
6
7
# 2.实现完美的实现深拷贝
先来实现一个简单的 simpleDeepClone
function simpleDeepClone (obj) {
debugger
const target = {}
for (let key in obj) {
if (typeof obj[key] === 'object') {
target[key] = simpleDeepClone(obj[key])
} else {
target[key] = obj[key]
}
}
return target;
}
let o = {
a: 1,
b: {c:{d:1}}
}
let cloneObj = simpleDeepClone(o)
console.log(cloneObj); // { a: 1, b: { c: { d: 1 } } }
// 修改值不影响克隆后的值,表明我们这确实是深拷贝
o.b.c.d = 10
console.log(o); // { a: 1, b: { c: { d: 10 } } }
console.log(cloneObj); // { a: 1, b: { c: { d: 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
但是上述代码虽然实现了简单的深拷贝,但是还存在下面的情况没有考虑:
- 没有考虑null的情况
- 没有兼容数组
- 没有考虑对象属性循环引用的情况
- 没有考虑属性为symbol类型的情况
- typeof Set和typeof Map都是 object ,所以也得考虑ES6中Set和Map的兼容
- 对Date对象,正则、函数的兼容
# 兼容null的情况
function simpleDeepClone (obj) {
if (obj === null) return null;
const target = {}
for (let key in obj) {
if (typeof obj[key] === 'object') {
target[key] = simpleDeepClone(obj[key])
} else {
target[key] = obj[key]
}
}
return target;
}
let o = {
a: 1,
b: {c:{d:1}},
d: null
}
let cloneObj = simpleDeepClone(o)
console.log(cloneObj); // { a: 1, b: { c: { d: 1 } }, d: null }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 兼容数组的情况
function simpleDeepClone (obj) {
if (obj === null) return null;
let target = null
let type = Object.prototype.toString.call(obj)
switch(type) {
case '[object Array]': // 兼容了数组
target = []
break;
case '[object Object]':
target = {}
break;
}
for (let key in obj) {
if (typeof obj[key] === 'object') {
target[key] = simpleDeepClone(obj[key])
} else {
target[key] = obj[key]
}
}
return target;
}
let o = {
a: 1,
b: {c:{d:4,g:[1]},m: 7},
e: null,
f: [1,2,3,[4,[5]],[6]]
}
let cloneObj = simpleDeepClone(o)
for (let key in cloneObj) {
console.log(cloneObj[key]);
}
// 1
// { c: { d: 4, g: [ 1 ] }, m: 7 }
// null
// [ 1, 2, 3, [ 4, [ 5 ] ], [ 6 ] ]
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
这里稍微解释下属性b: {c:{d:4,g:[1]},m: 7}
递归的过程,每一次递归都会调用simpleDeepClone
这个函数,当函数在嵌套调用时,会发生这样的事情:
- 当前函数被暂停;
- 与它关联的执行上下文被一个叫做 执行上下文堆栈 的特殊数据结构保存;
- 执行嵌套调用;
- 嵌套调用结束后,从堆栈中恢复之前的执行上下文,并从停止的位置恢复外部函数。
那么遍历b: {c:{d:4,g:[1]},m: 7}
这个嵌套的对象属性发生了什么事情呢?
- typeof b是一个对象,所以将simpleDeepClone({c:{d:4,g:[1]},m: 7})压入执行栈
- typeof c是一个对象,所以将simpleDeepClone({d:4,g:[1]})
- typeof d 是基本类型,直接赋值,所以此时target={d: 4},但是typeof g是一个数组,所以当前函数暂停执行
- typeof g是一个数组,所以将simpleDeepClone(g:[1]) 压入执行栈
此时所有子调用函数全部运行完毕,开始从暂停的地方开始执行,执行顺序是4321:
- 执行 --> target4 = [1]
- 执行 --> target3 = { d: 4, g: [ 1 ] } 补充:这里实际上是当前的target={d: 4},已经有了d变量 然后执行target[g] = target4
- 执行 --> target2 = { c: { d: 4, g: [ 1 ] }, m: 7 } 补充:同理执行target[c] = target3,同时c遍历完毕,遍历m,发现m是基本类型,执行target[m] = 7
- 执行 --> target1 = b: {c:{d:4,g:[1]},m: 7} 补充:同理,执行target[b] = target2
最终整个执行栈执行完毕,b属性被完美复制!
# 没有考虑对象属性循环引用的情况
利用解决循环引用的思路,就是在赋值之前判断当前值是否已经存在,避免循环引用,这里我们可以使用ES6的WeakMap
来生成一个hash表
function simpleDeepClone(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用,保存已经遍历过的对象
if (typeof obj === "object" || typeof obj === "function") {
let target = null;
let type = Object.prototype.toString.call(obj);
switch (type) {
case "[object Array]": // 兼容了数组
target = [];
break;
case "[object Object]":
target = {};
break;
}
console.log(target);
hash.set(obj, target); // (1)
for (let key in obj) {
if (typeof obj[key] === "object") {
target[key] = simpleDeepClone(obj[key], hash); // (2)
} else {
target[key] = obj[key];
}
}
console.log(target)
return target;
}
}
let o = {
b: {c:{d:4,g:[1]},m: 7},
}
o.h = o;
console.log(o); // { b: { c: { d: 4, g: [Array] }, m: 7 }, h: [Circular] }
let cloneObj = simpleDeepClone(o);
console.log(cloneObj); // { b: { c: { d: 4, g: [Array] }, m: 7 }, h: [Circular] }
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
值得注意的是(1)处,hash.set(obj,target)
中的target
其实是undefined
,但是在target在(2)
处被赋值,target
指向同一个内存地址,hash.set(obj,target)
中的target
值也改变。
这样就完美的解决了循环引用的问题
# 兼容symbol的情况
function simpleDeepClone(obj, hash = new WeakMap()) {
let target = null;
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用,hash表中有的直接返回改对象
let symKeys = Object.getOwnPropertySymbols(obj) // symbols类型单独处理
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof obj[symKey] === 'obect') {
target[symKey] = simpleDeepClone(obj[symKey],hash)
} else {
target[symKey] = obj[symKey]
console.log(target);
}
})
}
if (typeof obj === "object" || typeof obj === "function") {
let result;
let type = Object.prototype.toString.call(obj);
switch (type) {
case "[object Array]": // 兼容了数组
target = [];
break;
case "[object Object]":
target = {};
break;
}
hash.set(obj, target); // 遍历过的值存入hash表中
} else {
return obj;
}
console.log(111,target);
for (let key in obj) {
if (typeof obj[key] === "object") {
target[key] = simpleDeepClone(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
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
41
42
# 兼容Set和Map的情况
function simpleDeepClone(obj, hash = new WeakMap()) {
let target = null;
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用,hash表中有的直接返回改对象
let symKeys = Object.getOwnPropertySymbols(obj) // symbols类型单独处理
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof obj[symKey] === 'obect') {
target[symKey] = simpleDeepClone(obj[symKey],hash)
} else {
target[symKey] = obj[symKey]
console.log(target);
}
})
}
if (typeof obj === "object" || typeof obj === "function") {
let result;
let type = Object.prototype.toString.call(obj);
switch (type) {
case "[object Array]": // 兼容了数组
target = [];
break;
case "[object Object]":
target = {};
break;
case "[object Map]":
result = new Map(); // 处理map
obj.forEach((value, key) => {
result.set(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Set]": // 处理set
result = new Set();
obj.forEach((value, key) => {
result.add(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Date]":
return new Date(obj); // 处理日期
break;
default:
return obj; // 正则和函数一般层级不深,直接返回
break;
}
hash.set(obj, target); // 遍历过的值存入hash表中
} else {
return obj;
}
console.log(111,target);
for (let key in obj) {
if (typeof obj[key] === "object") {
target[key] = simpleDeepClone(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 兼容Date对象,正则、函数
// 对Date对象,正则、函数的兼容
function simpleDeepClone(obj, hash = new WeakMap()) {
let target = null;
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用,hash表中有的直接返回改对象
let symKeys = Object.getOwnPropertySymbols(obj) // symbols类型单独处理
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof obj[symKey] === 'obect') {
target[symKey] = simpleDeepClone(obj[symKey],hash)
} else {
target[symKey] = obj[symKey]
console.log(target);
}
})
}
if (typeof obj === "object" || typeof obj === "function") {
let result;
let type = Object.prototype.toString.call(obj);
switch (type) {
case "[object Array]": // 兼容了数组
target = [];
break;
case "[object Object]":
target = {};
break;
case "[object Map]":
result = new Map(); // 处理map
obj.forEach((value, key) => {
result.set(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Set]": // 处理set
result = new Set();
obj.forEach((value, key) => {
result.add(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Date]":
return new Date(obj); // 处理日期
break;
default:
return obj; // 正则和函数一般层级不深,直接返回
break;
}
hash.set(obj, target); // 遍历过的值存入hash表中
} else {
return obj;
}
for (let key in obj) {
if (typeof obj[key] === "object") {
target[key] = simpleDeepClone(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
这就是最后较为完整的版本,后期有时间再改良。
测试下:
function simpleDeepClone(obj, hash = new WeakMap()) {
let target = null;
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用,hash表中有的直接返回改对象
let symKeys = Object.getOwnPropertySymbols(obj) // symbols类型单独处理
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof obj[symKey] === 'obect') {
target[symKey] = simpleDeepClone(obj[symKey],hash)
} else {
target[symKey] = obj[symKey]
console.log(target);
}
})
}
if (typeof obj === "object" || typeof obj === "function") {
let result;
let type = Object.prototype.toString.call(obj);
switch (type) {
case "[object Array]": // 兼容了数组
target = [];
break;
case "[object Object]":
target = {};
break;
case "[object Map]":
result = new Map(); // 处理map
obj.forEach((value, key) => {
result.set(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Set]": // 处理set
result = new Set();
obj.forEach((value, key) => {
result.add(key, simpleDeepClone(value, hash));
});
return result;
break;
case "[object Date]":
return new Date(obj); // 处理日期
break;
default:
return obj; // 正则和函数一般层级不深,直接返回
break;
}
hash.set(obj, target); // 遍历过的值存入hash表中
} else {
return obj;
}
for (let key in obj) {
if (typeof obj[key] === "object") {
target[key] = simpleDeepClone(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
let temp = Symbol("我是一个symbol");
let m = new Map();
m.set(1, "1");
m.set(2, "2");
let s = new Set();
s.add(1, "1");
s.add(2, "2");
let o = {
i: new Set(),
nul: null,
b: { c: { d: 4, g: [1] }, m: 7 },
p: /'resss'/,
func: function () {}
};
o.temp = "1111";
o.m = m;
o.s = s
o.h = o
let cloneObj = simpleDeepClone(o);
cloneObj.p = '我修改了'
console.log(o);
// {
// i: Set {},
// nul: null,
// b: { c: { d: 4, g: [Array] }, m: 7 },
// p: /'resss'/,
// func: [Function: func],
// temp: '1111',
// m: Map { 1 => '1', 2 => '2' },
// s: Set { 1, 2 },
// h: [Circular]
// }
console.log(cloneObj);
// {
// i: Set {},
// nul: null,
// b: { c: { d: 4, g: [Array] }, m: 7 },
// p: '我修改了',
// func: [Function: func],
// temp: '1111',
// m: Map { 1 => '1', 2 => '2' },
// s: Set { 1, 2 },
// h: [Circular]
// }
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
结果很完美!