实现深拷贝(deepClone)
# 实现深拷贝(deepClone)
# 功能描述
深克隆(deepClone),层层拷贝对象或数组的每一层内容。
注意:对于引用值时,深克隆之后不会出现你改我也改的情况。
# 手写实现(简易版)
let newObj = JSON.parse(JSON.stringify(oldObj));
1
局限性:
- 如果对象有循环引用,拷贝时会报错。
const obj = { name: "张三" }; obj.details = obj; let newObj = JSON.parse(JSON.stringify(obj)); // Uncaught TypeError: Converting circular structure to JSON
1
2
3
4
5 - 会抛弃对象的
constructor
,所有的构造函数会指向Object
。 - 无法拷贝一些特殊的对象,诸如 RegExp,Date,Set,Map 等。
- 无法拷贝函数(划重点)。
# 手写实现(面试版)
解决上面提到的前两个问题:
- 解决拷贝循环引用问题
- 解决拷贝对应原型问题
/**
* @param {*} target 需要被拷贝的对象
* @param {*} hash 性能考虑不用 Map,使用弱引用的 WeakMap
* @returns
*/
function deepClone(target, hash = new WeakMap) {
// null 和 undefiend 是不需要拷贝的
if (target == null) { return target; }
// RegExp 和 Date 这两种特殊值暂不考虑
if (target instanceof RegExp) { return new RegExp(target) }
if (target instanceof Date) { return new Date(target) }
// 基本数据类型直接返回即可,函数暂不考虑
if (typeof target != 'object') return target;
// 针对 [] {} 两种类型,基于它们的构造函数来实例化一个新的对象实例
let clonedTarget = new target.constructor();
// 说明是一个已经拷贝过的对象,那么直接返回即可,防止循环引用
if (hash.get(target)) {
return hash.get(target)
}
// 记录下已经拷贝过的对象
hash.set(target, clonedTarget);
// 遍历对象的 key,in 会遍历当前对象上的属性 和 __proto__ 指向的属性
for (let key in target) {
// 如果 key 是对象自有的属性
if (target.hasOwnProperty(key)) {
// 如果值依然是对象,就继续递归拷贝
clonedTarget[key] = deepClone(target[key], hash);
}
}
return clonedTarget
}
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
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
关于强引用与弱引用
- 如果用的是 Map 来创建 map 对象,那么里面的 key 和 map 构成了强引用关系。对于强引用来说,只要这个强引用还在,那么对象就无法被回收。在上面的例子中,在程序结束之前,因为是用一个个对象来作为 map 的 key 的,所以这些对象所占的内存空间一直不会被释放。
- 如果用的是 WeakMap 这一种特殊的 Map,那么其中的 key 和 map 构成了弱引用(其键必须是对象,而值可以是任意的),被弱引用的对象可以在任何时候被回收。
- 同时,Map 类型在访问、插入、删除元素时,需要进行迭代器解引用操作,这也会导致性能下降;而 WeakMap 在进行这些操作时不需要进行解引用操作,因此可以提供更好的性能。
显然,使用 WeakMap(弱引用)来存储元素,可以避免强引用导致的内存泄漏问题,同时这种类型在查找特定元素时也可以更快地响应。
# 手写实现(完整版)
在上面的基础上,进一步实现:
- 能够拷贝特殊对象
- 能够拷贝函数
# 前置准备
首先对于特殊对象,要知道这个对象是不是可遍历的,因为这将决定采用不同的处理逻辑。
// 对于特殊对象,使用这个方式来鉴别
Object.prototype.toString.call(obj);
// 可遍历对象的返回结果
'[object Map]'
'[object Set]'
'[object Array]'
'[object Object]'
'[object Arguments]'
// 不可遍历的对象的返回结果
'[object Boolean]'
'[object Number]'
'[object String]'
'[object Date]'
'[object Error]'
'[object RegExp]'
'[object Function]'
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
其次对于函数的拷贝:
- 虽然函数也是对象,但是它过于特殊,需要单独把它拿出来拆解。
- JS 有两种函数,一种是普通函数,另一种是箭头函数。
- 每个普通函数都是 Function 的实例。
- 箭头函数不是任何类的实例,每次调用都是不一样的引用。
- 因此只需要处理普通函数的情况,箭头函数直接返回它本身就好了。
- 利用原型来区分两者,因为箭头函数是不存在原型的。
# 完整代码
// 获取对象类型
const getType = (obj) => Object.prototype.toString.call(obj);
// 判断是基本数据类型还是引用数据类型
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
// 判断是否可遍历
const canTraverse = {
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
};
const mapTag = '[object Map]';
const setTag = '[object Set]';
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
// 处理 RegExp 对象
// 返回一个新的 RegExp 对象,它继承了原始 RegExp 对象的属性和方法
// source:源字符串 flags:标志字符串
const handleRegExp = (target) => {
const { source, flags } = target;
return new target.constructor(source, flags);
}
// 处理函数
const handleFunc = (func) => {
// 箭头函数直接返回自身
if (!func.prototype) return func;
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (!body) return null;
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
}
// 处理不可遍历对象
const handleNotTraverse = (target, tag) => {
// 获取目标对象的构造函数,用于创建新的对象
const Ctor = target.constructor;
switch (tag) {
case boolTag:
return new Object(Boolean.prototype.valueOf.call(target));
case numberTag:
return new Object(Number.prototype.valueOf.call(target));
case stringTag:
return new Object(String.prototype.valueOf.call(target));
case symbolTag:
return new Object(Symbol.prototype.valueOf.call(target));
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return handleRegExp(target);
case funcTag:
return handleFunc(target);
default:
return new Ctor(target);
}
}
/**
* 在前面的深拷贝实现上,加入了对特殊对象和函数的支持
* @param {*} target 需要被拷贝的对象
* @param {*} hash 性能考虑不用 Map,使用弱引用的 WeakMap
* @returns
*/
const deepClone = (target, hash = new WeakMap()) => {
if (!isObject(target))
return target;
let type = getType(target);
let cloneTarget;
if (!canTraverse[type]) {
// 处理不能遍历的对象
return handleNotTraverse(target, type);
} else {
// 这一步很关键,可以保证对象的原型不丢失
let ctor = target.constructor;
cloneTarget = new ctor();
}
if (hash.get(target))
return target;
hash.set(target, true);
if (type === mapTag) {
// 处理 Map
target.forEach((item, key) => {
cloneTarget.set(deepClone(key, hash), deepClone(item, hash));
})
}
if (type === setTag) {
// 处理 Set
target.forEach(item => {
cloneTarget.add(deepClone(item, hash));
})
}
// 处理数组和对象
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], hash);
}
}
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
113
114
115
116
117
118
119
120
121
122
123
124
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
113
114
115
116
117
118
119
120
121
122
123
124
(完)