实现一个高效的deepClone

先来实现一个最简版的 deepClone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deepClone(obj) {
if (
obj == null ||
typeof obj == "string" ||
typeof obj == "boolean" ||
typeof obj == "number"
) {
return obj;
}
if (Array.isArray(obj)) {
let temp = [];
for (let i = 0; i < obj.length; i++) {
temp[i] = deepClone(obj[i]);
}
return temp;
} else if (typeof obj == "object") {
let temp = {};
for (let k of Object.keys(obj)) {
temp[k] = deepClone(obj[k]);
}
return temp;
}
}

迭代版本的 deepClone

最近 EDA 项目在解决一些性能上的问题,首当其冲的就是 deepClone,各种地方都在用,且十分卡性能。如何解决这个问题呢?

由于我们项目中的 deepClone 是一个递归版本的 deepClone,所以应该可以通过把它改成迭代版的 deepClone 来提速。

用栈来实现迭代。函数调用栈本身也是栈,但开销肯定比自己写的栈要大一些。

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
function toString(a) {
return Object.prototype.toString.call(a).slice(8, -1);
}

function isObj(a) {
return toString(a) === "Object";
}

function isNum(a) {
return toString(a) === "Number";
}

function isStr(a) {
return toString(a) === "String";
}

function isBool(a) {
return toString(a) === "Boolean";
}

function isSymbol(a) {
return toString(a) === "Symbol";
}

function isBigInt(a) {
return toString(a) === "BigInt";
}

function isFunction(a) {
return toString(a) === "Function";
}

function isRegExp(a) {
return toString(a) === "RegExp";
}

function isDate() {
return toString(a) === "Date";
}

function isError() {
return toString(a) === "Error";
}

function

function isPrim(a) {
return (
isNum(a) ||
isStr(a) ||
isBool(a) ||
isSymbol(a) ||
isBigInt(a) ||
isFunction(a) ||
a === null ||
a === void 0
);
}

function deepCloneStack(obj) {
if (isPrim(obj)) {
return obj;
}
const root = Array.isArray(obj) ? [] : {};
const stack = [[{ root }, obj, "root"]];

while (stack.length > 0) {
let [target, obj, key] = stack.pop();
if (Array.isArray(obj)) {
if (!target[key]) {
target[key] = new Array(obj.length);
}
for (let k = 0; k < obj.length; k++) {
if (isPrim(obj[k])) {
target[key][k] = obj[k];
} else {
stack.push([target[key], obj[k], k]);
}
}
} else {
if (!target[key]) {
target[key] = {};
}
for (let k in obj) {
if (isPrim(obj[k])) {
target[key][k] = obj[k];
} else {
stack.push([target[key], obj[k], k]);
}
}
}
}
return root;
}

在线运行地址

不过很遗憾的是迭代版本速度比递归还要慢,估计是因为 js 的数组性能太差导致的。