-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
106 lines (88 loc) · 2.67 KB
/
index.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
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
'use strict';
const isPrimitive = (val) => typeof val !== 'object' || val === null;
const getTypeOf = (obj) => {
const type = Object.prototype.toString.call(obj);
// [object xxx] typename starts at 8
return type.slice(8, -1);
};
const compositeType = ['Object', 'Array', 'Map', 'WeakMap', 'Set', 'WeakSet'];
// Handler to clone Date object
const cloneDate = (date) => new Date(date.getTime());
// Handler to clone RegExp object
const cloneRegExp = (re) => {
const cloned = new RegExp(re.source, re.flags);
cloned.lastIndex = re.lastIndex;
return cloned;
};
// Handler to clone Promise object
const clonePromise = (promise, cloning) =>
new Promise((resolve, reject) => {
promise.then(
(value) => {
resolve(isPrimitive(value) ? value : cloning(value));
},
(error) => {
reject(isPrimitive(error) ? error : cloning(error));
},
);
});
// Types needed to be handled specially
const cloneHandlers = {
Date: cloneDate,
RegExp: cloneRegExp,
Promise: clonePromise
};
const initializeClonedObject = (obj) => {
const type = getTypeOf(obj);
let initVal = null;
if (compositeType.includes(type)) {
initVal = eval(`new ${type}()`);
}
return initVal;
};
const clone = (obj) => {
// Return the obj if it is actually a primitive
if (isPrimitive(obj)) return obj;
// Doing the actual cloning work
const objRefs = [];
const map = new Map();
const cloning = (target) => {
// If target has been cloned
if (objRefs.indexOf(target) !== -1) {
return map.get(target);
}
objRefs.push(target);
const cloned = initializeClonedObject(target);
map.set(target, cloned);
const type = getTypeOf(target);
if (compositeType.includes(type)) {
// Loops through the properties of any composite object
for (const key in target) {
cloned[key] = isPrimitive(target[key])
? target[key]
: cloning(target[key]);
}
// Additional operations for `Map`
if (type === 'Map') {
for (const [key, value] of target.entries()) {
cloned.set(key, isPrimitive(value) ? value : cloning(value));
}
}
// Additional operations for `Set`
if (type === 'Set') {
for (const key of target.keys()) {
cloned.add(isPrimitive(key) ? key : cloning(key));
}
}
// The `WeakSet` and `WeakMap` cann't be cloned
if (['WeakSet', 'WeakMap'].includes(type)) {
throw new Error(`${type} can not be cloned`);
}
return cloned;
}
// Process other types such as `Date`, `RegExp`, `Promise`
return cloneHandlers[type](target, cloning);
};
return cloning(obj);
};
module.exports = clone;