@superhero/deep 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/clone.js +59 -12
- package/clone.test.js +42 -1
- package/merge.js +1 -1
- package/package.json +1 -1
package/clone.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
export default function clone(input)
|
|
1
|
+
export default function clone(input, options = {})
|
|
2
2
|
{
|
|
3
3
|
const seen = new WeakSet()
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
options.preservesImutable = options.preservesImutable ?? false
|
|
6
|
+
options.preservesEnumerable = options.preservesEnumerable ?? true
|
|
7
|
+
options.fallback = options.fallback ?? cloneFallback.bind(null, options, seen)
|
|
8
|
+
|
|
9
|
+
return deepClone(input, options, seen)
|
|
5
10
|
}
|
|
6
11
|
|
|
7
|
-
function deepClone(value, seen)
|
|
12
|
+
function deepClone(value, options, seen)
|
|
8
13
|
{
|
|
9
14
|
switch(Object.prototype.toString.call(value))
|
|
10
15
|
{
|
|
11
|
-
case '[object Array]' : return cloneArray(value, seen)
|
|
12
|
-
case '[object Object]' : return cloneObject(value, seen)
|
|
16
|
+
case '[object Array]' : return cloneArray(value, options, seen)
|
|
17
|
+
case '[object Object]' : return cloneObject(value, options, seen)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
return
|
|
20
|
+
return options.fallback(value)
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
function cloneArray(array, seen)
|
|
23
|
+
function cloneArray(array, options, seen)
|
|
19
24
|
{
|
|
20
25
|
if(seen.has(array))
|
|
21
26
|
{
|
|
@@ -23,10 +28,10 @@ function cloneArray(array, seen)
|
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
seen.add(array)
|
|
26
|
-
return array.map((item) => deepClone(item, seen))
|
|
31
|
+
return array.map((item) => deepClone(item, options, seen))
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function cloneObject(obj, seen)
|
|
34
|
+
function cloneObject(obj, options, seen)
|
|
30
35
|
{
|
|
31
36
|
if(seen.has(obj))
|
|
32
37
|
{
|
|
@@ -39,10 +44,52 @@ function cloneObject(obj, seen)
|
|
|
39
44
|
|
|
40
45
|
for(const key of Object.getOwnPropertyNames(obj))
|
|
41
46
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
if(options.preservesImutable)
|
|
48
|
+
{
|
|
49
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
|
|
50
|
+
Object.defineProperty(output, key,
|
|
51
|
+
{
|
|
52
|
+
enumerable : options.preservesEnumerable ? descriptor.enumerable : true,
|
|
53
|
+
writable : descriptor.writable,
|
|
54
|
+
configurable : descriptor.configurable,
|
|
55
|
+
value : deepClone(descriptor.value, options, seen)
|
|
56
|
+
})
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
else if(options.preservesEnumerable)
|
|
60
|
+
{
|
|
61
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
|
|
62
|
+
Object.defineProperty(output, key,
|
|
63
|
+
{
|
|
64
|
+
enumerable : descriptor.enumerable,
|
|
65
|
+
writable : true,
|
|
66
|
+
configurable : true,
|
|
67
|
+
value : deepClone(descriptor.value, options, seen)
|
|
68
|
+
})
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
output[key] = deepClone(obj[key], options, seen)
|
|
74
|
+
}
|
|
45
75
|
}
|
|
46
76
|
|
|
47
77
|
return output
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cloneFallback(options, seen, value)
|
|
81
|
+
{
|
|
82
|
+
if(typeof value !== 'object'
|
|
83
|
+
|| value === null)
|
|
84
|
+
{
|
|
85
|
+
return value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const clone = Object.create(Object.getPrototypeOf(value))
|
|
89
|
+
for(const key in value)
|
|
90
|
+
{
|
|
91
|
+
clone[key] = deepClone(value[key], options, seen)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return clone
|
|
48
95
|
}
|
package/clone.test.js
CHANGED
|
@@ -24,6 +24,32 @@ suite('@superhero/deep/clone', () =>
|
|
|
24
24
|
assert.notStrictEqual(cloned.foo, obj.foo, 'Not the same reference as the original')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
test('Do not preserves descriptors', () =>
|
|
28
|
+
{
|
|
29
|
+
const origin = {}
|
|
30
|
+
|
|
31
|
+
Object.defineProperty(origin, 'foo', { value: {}, enumerable: true, writable: true, configurable: true })
|
|
32
|
+
Object.defineProperty(origin, 'bar', { value: {}, enumerable: false, writable: true, configurable: true })
|
|
33
|
+
Object.defineProperty(origin, 'baz', { value: {}, enumerable: false, writable: false, configurable: true })
|
|
34
|
+
Object.defineProperty(origin, 'qux', { value: {}, enumerable: false, writable: false, configurable: false })
|
|
35
|
+
|
|
36
|
+
const cloned = deepclone(origin, { preservesImutable: false, preservesEnumerable: false })
|
|
37
|
+
|
|
38
|
+
assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
|
|
39
|
+
assert.notStrictEqual(cloned.foo, origin.foo, 'Cloned nested object should not share reference with the original')
|
|
40
|
+
|
|
41
|
+
const
|
|
42
|
+
clonedDescriptors = Object.getOwnPropertyDescriptors(cloned),
|
|
43
|
+
originDescriptors = Object.getOwnPropertyDescriptors(origin)
|
|
44
|
+
|
|
45
|
+
for(const key in originDescriptors)
|
|
46
|
+
{
|
|
47
|
+
assert.equal(clonedDescriptors[key].enumerable, true)
|
|
48
|
+
assert.equal(clonedDescriptors[key].writable, true)
|
|
49
|
+
assert.equal(clonedDescriptors[key].configurable, true)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
27
53
|
test('Preserves descriptors', () =>
|
|
28
54
|
{
|
|
29
55
|
const origin = {}
|
|
@@ -33,7 +59,7 @@ suite('@superhero/deep/clone', () =>
|
|
|
33
59
|
Object.defineProperty(origin, 'baz', { value: {}, enumerable: false, writable: false, configurable: true })
|
|
34
60
|
Object.defineProperty(origin, 'qux', { value: {}, enumerable: false, writable: false, configurable: false })
|
|
35
61
|
|
|
36
|
-
const cloned = deepclone(origin)
|
|
62
|
+
const cloned = deepclone(origin, { preservesImutable: true, preservesEnumerable: true })
|
|
37
63
|
|
|
38
64
|
assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
|
|
39
65
|
assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
|
|
@@ -51,6 +77,21 @@ suite('@superhero/deep/clone', () =>
|
|
|
51
77
|
}
|
|
52
78
|
})
|
|
53
79
|
|
|
80
|
+
test('Does not preserve frozen object state', () =>
|
|
81
|
+
{
|
|
82
|
+
const origin = { foo:'bar', baz: 42, qux: true }
|
|
83
|
+
|
|
84
|
+
Object.freeze(origin)
|
|
85
|
+
|
|
86
|
+
assert.ok(Object.isFrozen(origin), 'Origin object should be frozen')
|
|
87
|
+
|
|
88
|
+
const cloned = deepclone(origin)
|
|
89
|
+
|
|
90
|
+
assert.deepStrictEqual(cloned, origin, 'Cloned nested object should be equal to the original')
|
|
91
|
+
assert.notStrictEqual(cloned, origin, 'Not the same reference as the original')
|
|
92
|
+
assert.ok(false === Object.isFrozen(cloned), 'Cloned object should not be frozen')
|
|
93
|
+
})
|
|
94
|
+
|
|
54
95
|
test('Clones arrays', () =>
|
|
55
96
|
{
|
|
56
97
|
const
|
package/merge.js
CHANGED