-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
/
deepCyclicCopy.js
105 lines (88 loc) · 2.7 KB
/
deepCyclicCopy.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
/**
* Copyright (c) 2017-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
const EMPTY = new Set();
export type DeepCyclicCopyOptions = {|
blacklist: Set<string>,
keepPrototype: boolean,
|};
// $FlowFixMe: Node 6 does not have gOPDs, so we define a simple polyfill for it.
if (!Object.getOwnPropertyDescriptors) {
// $FlowFixMe: polyfill
Object.getOwnPropertyDescriptors = obj => {
const list = {};
Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj))
// $FlowFixMe: assignment with a Symbol is OK.
.forEach(key => (list[key] = Object.getOwnPropertyDescriptor(obj, key)));
return list;
};
}
export default function deepCyclicCopy(
value: any,
options?: DeepCyclicCopyOptions = {blacklist: EMPTY, keepPrototype: false},
cycles: WeakMap<any, any> = new WeakMap(),
): any {
if (typeof value !== 'object' || value === null) {
return value;
} else if (cycles.has(value)) {
return cycles.get(value);
} else if (Array.isArray(value)) {
return deepCyclicCopyArray(value, options, cycles);
} else {
return deepCyclicCopyObject(value, options, cycles);
}
}
function deepCyclicCopyObject(
object: Object,
options: DeepCyclicCopyOptions,
cycles: WeakMap<any, any>,
): Object {
const newObject = options.keepPrototype
? Object.create(Object.getPrototypeOf(object))
: {};
// $FlowFixMe: Object.getOwnPropertyDescriptors is polyfilled above.
const descriptors = Object.getOwnPropertyDescriptors(object);
cycles.set(object, newObject);
Object.keys(descriptors).forEach(key => {
if (options.blacklist && options.blacklist.has(key)) {
delete descriptors[key];
return;
}
const descriptor = descriptors[key];
if (typeof descriptor.value !== 'undefined') {
descriptor.value = deepCyclicCopy(
descriptor.value,
{blacklist: EMPTY, keepPrototype: options.keepPrototype},
cycles,
);
}
descriptor.configurable = true;
});
return Object.defineProperties(newObject, descriptors);
}
function deepCyclicCopyArray(
array: Array<any>,
options: DeepCyclicCopyOptions,
cycles: WeakMap<any, any>,
): Array<any> {
const newArray = options.keepPrototype
? // $FlowFixMe: getPrototypeOf an array is OK.
new (Object.getPrototypeOf(array)).constructor(array.length)
: [];
const length = array.length;
cycles.set(array, newArray);
for (let i = 0; i < length; i++) {
newArray[i] = deepCyclicCopy(
array[i],
{blacklist: EMPTY, keepPrototype: options.keepPrototype},
cycles,
);
}
return newArray;
}