-
Notifications
You must be signed in to change notification settings - Fork 1
/
deepClone.js
81 lines (69 loc) · 1.91 KB
/
deepClone.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
function deepClone (...sources) {
const result = {}
for (const source of sources) {
merge(result, source)
}
return result
}
function merge (output, input) {
const props = Object.keys(input)
for (const prop of props) {
// Prevents Prototype Pollution
if (prop === '__proto__') continue
const descriptor = Object.getOwnPropertyDescriptor(input, prop)
const value = descriptor.value
if (value) descriptor.value = cloneDescriptorValue(value)
Object.defineProperty(output, prop, descriptor)
}
return output
}
// Creates a deep clone for each value
function cloneDescriptorValue (value) {
// Arrays
if (objectType(value) === '[object Array]') {
const array = []
for (let v of value) {
v = cloneDescriptorValue(v)
array.push(v)
}
return array
}
// Objects
if (objectType(value) === '[object Object]') {
const obj = {}
const props = Object.keys(value)
for (const prop of props) {
const descriptor = Object.getOwnPropertyDescriptor(value, prop)
if (descriptor.value) descriptor.value = cloneDescriptorValue(descriptor.value)
Object.defineProperty(obj, prop, descriptor)
}
return obj
}
// Other Types of Objects
if (objectType(value) === '[object Date]') {
return new Date(value.getTime())
}
if (objectType(value) === '[object Map]') {
const map = new Map()
for (const entry of value) {
map.set(entry[0], cloneDescriptorValue(entry[1]))
}
return map
}
if (objectType(value) === '[object Set]') {
const set = new Set()
for (const entry of value.entries()) {
set.add(cloneDescriptorValue(entry[0]))
}
return set
}
// Types we don't need to clone or cannot clone.
// Examples:
// - Primitives don't need to clone
// - Functions cannot clone
return value
}
function objectType (value) {
return Object.prototype.toString.call(value)
}
export default deepClone