# 定义
浅拷贝:创建一个新对象,对原始对象精确拷贝。如果是基础类型,拷贝基础类型的值,如果是引用类型,拷贝内存地址
深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象
# 浅拷贝
function clone (target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 深拷贝
# 乞丐版
JSON.parse(JSON.stringify());
1
缺陷:
- 拷贝其他引用类型
- 拷贝函数
- 循环引用
# 基础版
- 在浅拷贝的基础上,如果是引用类型,则创建一个新对象,遍历克隆对象的属性,深拷贝后依次添加到新对象上
- 使用递归深拷贝对象
function clone (target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]); // 递归
}
return cloneTarget;
} else {
return target;
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 考虑数组
function clone (target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}; // 考虑数组
for (const key in target) {
cloneTarget[key] = clone(target[key]); // 递归
}
return cloneTarget;
} else {
return target;
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 循环引用
递归死循环会导致内存溢出
使用 Map
来存储已经拷贝过的值
function clone (target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}; // 考虑数组
// 检查是否已经拷贝过,有的话直接返回
if (map.has(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map); // 递归
}
return cloneTarget;
} else {
return target;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# WeakMap
WeakMap
的键为弱引用。
弱引用,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则是不可访问的,因此可以在任何时刻被回收。
let obj = { name: 'obj' };
const target = new Map();
target.set(obj, 'target');
obj = null;
// 此时虽然手动进行释放,但是
// target 依然对 obj 存在强引用关系,所以这部分内存依然无法释放
1
2
3
4
5
6
2
3
4
5
6
let obj = { name: 'obj' };
const target = new WeakMap();
target.set(obj, 'target');
obj = null;
// WeakMap 的话,target 和 obj 存在的是弱引用关系
// 当下一次垃圾回收机制执行时,这块内存就会被释放
1
2
3
4
5
6
2
3
4
5
6
之前代码改写为:
function clone (target, map = new WeakMap()) {
...
}
1
2
3
2
3
# 性能优化
for...in
在遍历时效率低,while
> for
> for...in
所以使用 while
来替换 for...in
// 使用 `while` 实现一个 `forEach`,当遍历数组时,使用 forEach
function forEach (array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index)
}
return array;
}
function clone (target, map = new WeakMap()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {}; // 考虑数组
// 检查是否已经拷贝过,有的话直接返回
if (map.has(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
const keys = isArray ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map); // 递归
});
return cloneTarget;
} else {
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
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
# 其它数据类型
# 可继续遍历引用类型
我们只考虑了 object
和 array
,还有其它的引用类型需要考虑
// 判断是否为引用类型
function isObject (target) {
return target !== null && (typeof target === 'object');
}
// 获取数据类型
function getType(target) {
return Object.prototype.toString.call(target).slice(8, -1);
}
// 保留对象原型上的数据,若使用 cloneTarget = [] || {} 则会丢失原型
function getInit (target) {
const Ctor = target.constructor;
return new Ctor();
}
// 可继续遍历类型
const deepTag = ['Object', 'Array', 'Map', 'Set', 'Arguments'];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
改写之前代码:
function clone (target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
// 判断是否可以继续遍历
if (deepTag.includes(type)) {
cloneTarget = getInit(target);
}
// 检查是否已经拷贝过,有的话直接返回
if (map.has(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆 Set
if (type === 'Set') {
target.forEach(value => {
cloneTarget.add(clone(value));
});
}
// 克隆 Map
if (type === 'Map') {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value));
});
}
// 克隆数组和对象
const keys = type === 'Array' ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
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
41
42
43
44
45
46
47
48
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
# 不可继续遍历引用类型
function cloneReg (target) {
const reFlags = /\w*$/;
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
function cloneSymbol (target) {
return Object(Symbol.prototype.valueOf.call(target));
}
function cloneOtherType (target, type) {
const Ctor = target.constructor;
switch (type) {
case 'Boolean':
case 'Number':
case 'String':
case 'Error':
case 'Date':
return new Ctor(target);
case 'RegExp':
return cloneReg(target);
case 'Symbol':
return cloneSymbol(target);
default:
return null;
}
}
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
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
改写之前代码:
function clone (target, map = new WeakMap()) {
...
// 判断是否可以继续遍历
if (deepTag.includes(type)) {
cloneTarget = getInit(target);
} else {
return cloneOtherType(target, type);
}
...
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 克隆函数
可直接返回,也可以构造一个新函数
function cloneOtherType (target, type) {
const Ctor = target.constructor;
switch (type) {
case 'Boolean':
case 'Number':
case 'String':
case 'Error':
case 'Date':
return new Ctor(target);
case 'RegExp':
return cloneReg(target);
case 'Symbol':
return cloneSymbol(target);
case 'Function':
return target;
default:
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 完整代码
// 使用 `while` 实现一个 `forEach`,当遍历数组时,使用 forEach
function forEach (array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index)
}
return array;
}
// 判断是否为引用类型
function isObject (target) {
return target !== null && (typeof target === 'object');
}
// 获取数据类型
function getType(target) {
return Object.prototype.toString.call(target).slice(8, -1);
}
// 保留对象原型上的数据,若使用 cloneTarget = [] || {} 则会丢失原型
function getInit (target) {
const Ctor = target.constructor;
return new Ctor();
}
// 可继续遍历类型
const deepTag = ['Object', 'Array', 'Map', 'Set', 'Arguments'];
function cloneReg (target) {
const reFlags = /\w*$/;
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
function cloneSymbol (target) {
return Object(Symbol.prototype.valueOf.call(target));
}
// 克隆不可继续遍历引用类型
function cloneOtherType (target, type) {
const Ctor = target.constructor;
switch (type) {
case 'Boolean':
case 'Number':
case 'String':
case 'Error':
case 'Date':
return new Ctor(target);
case 'RegExp':
return cloneReg(target);
case 'Symbol':
return cloneSymbol(target);
case 'Function':
return target;
default:
return null;
}
}
function clone (target, map = new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
// 判断是否可以继续遍历
if (deepTag.includes(type)) {
cloneTarget = getInit(target);
} else {
return cloneOtherType(target, type);
}
// 检查是否已经拷贝过,有的话直接返回
if (map.has(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆 Set
if (type === 'Set') {
target.forEach(value => {
cloneTarget.add(clone(value));
});
}
// 克隆 Map
if (type === 'Map') {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value));
});
}
// 克隆数组和对象
const keys = type === 'Array' ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
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
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
111
112
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
111
112