3
3
* @module tutils/utils/clone
4
4
*/
5
5
6
- import type { Class , Optional } from '#src/types'
6
+ import type { PropertyDescriptor } from '#src/interfaces'
7
+ import type { Class , Objectify } from '#src/types'
7
8
import cast from './cast'
8
9
import define from './define'
9
10
import descriptor from './descriptor'
@@ -12,12 +13,14 @@ import isArray from './is-array'
12
13
import isArrayBuffer from './is-array-buffer'
13
14
import isBuffer from './is-buffer'
14
15
import isDataView from './is-data-view'
16
+ import isDate from './is-date'
15
17
import isFunction from './is-function'
16
18
import isMap from './is-map'
17
19
import isPrimitive from './is-primitive'
18
20
import isRegExp from './is-reg-exp'
19
21
import isSet from './is-set'
20
22
import isTypedArray from './is-typed-array'
23
+ import isUndefined from './is-undefined'
21
24
import properties from './properties'
22
25
23
26
/**
@@ -30,103 +33,121 @@ import properties from './properties'
30
33
* @todo document differences between structured clone algorithm
31
34
* @todo examples
32
35
*
33
- * @template T - Value type
36
+ * @template T - Cloned value type
34
37
*
35
38
* @param {T } value - Value to recursively clone
36
39
* @return {T } Deep cloned `value`
37
40
*/
38
41
const clone = < T > ( value : T ) : T => {
39
- // primitives do not need to be cloned
40
- if ( isPrimitive ( value ) ) return value
41
-
42
- // bind functions to empty objects to create clones
43
- if ( isFunction ( value ) ) return cast ( value . bind ( { } ) )
44
-
45
42
/**
46
- * Object to clone .
43
+ * Cloned values cache .
47
44
*
48
- * @const {T & object} obj
45
+ * @const {WeakMap<Objectify<any>, Objectify<any>>} cache
49
46
*/
50
- const obj : T & object = cast ( value )
47
+ const cache : WeakMap < Objectify < any > , Objectify < any > > = new WeakMap ( )
51
48
52
49
/**
53
- * Initializes a cloned object .
50
+ * Deep clones `value` .
54
51
*
55
- * @template T - Object type
52
+ * @template T - Value type
56
53
*
57
- * @param {T } obj - Object to clone
58
- * @return {T } Initialized clone
54
+ * @param {T } value - Value to deep clone
55
+ * @return {T } Deep cloned `value`
59
56
*/
60
- const init = < T extends object > ( obj : T ) : T => {
57
+ const dclone = < T > ( value : T ) : T => {
58
+ // primitives do not need to be cloned
59
+ if ( isPrimitive ( value ) ) return value
60
+
61
+ // bind functions to empty objects to create clones
62
+ if ( isFunction ( value ) ) return cast ( value . bind ( { } ) )
63
+
64
+ /**
65
+ * Object to clone.
66
+ *
67
+ * @const {Objectify<any> & T} obj
68
+ */
69
+ const obj : Objectify < any > & T = cast ( value )
70
+
61
71
/**
62
72
* Cloned object constructor.
63
73
*
64
74
* @const {Class<T>} Clone
65
75
*/
66
- const Clone : Class < T > = cast < Class < T > > ( obj . constructor )
76
+ const Clone : Class < Objectify < any > & T > = cast ( obj . constructor )
67
77
68
78
/**
69
- * Initialized clone .
79
+ * Cloned value .
70
80
*
71
- * @var {T} result
81
+ * @var {Objectify<any> & T} cloned
72
82
*/
73
- let result ! : T
83
+ let cloned ! : Objectify < any > & T
74
84
75
- // clone array value
76
- if ( isArray ( obj ) ) result = new Clone ( obj . length )
85
+ // init cloned array
86
+ if ( isArray ( obj ) ) cloned = new Clone ( obj . length )
77
87
78
- // clone array buffer
88
+ // init cloned array buffer
79
89
if ( isArrayBuffer ( obj ) ) {
80
- result = new Clone ( obj . byteLength )
81
- new Uint8Array ( cast ( result ) ) . set ( new Uint8Array ( obj ) )
90
+ cloned = new Clone ( obj . byteLength )
91
+ new Uint8Array ( cast ( cloned ) ) . set ( new Uint8Array ( obj ) )
82
92
}
83
93
84
- // clone buffer
85
- if ( isBuffer ( obj ) ) result = cast ( Uint8Array . prototype . slice . call ( obj ) )
94
+ // init cloned buffer
95
+ if ( isBuffer ( obj ) ) cloned = cast ( Uint8Array . prototype . slice . call ( obj ) )
86
96
87
- // clone dataview
97
+ // init cloned data view
88
98
if ( isDataView ( obj ) ) {
89
- result = new Clone ( init ( obj . buffer ) , obj . byteOffset , obj . byteLength )
99
+ cloned = new Clone ( dclone ( obj . buffer ) , obj . byteOffset , obj . byteLength )
90
100
}
91
101
92
- // clone regexp
93
- if ( isRegExp ( obj ) ) result = new Clone ( obj . source , obj . flags )
102
+ // init cloned date
103
+ if ( isDate ( obj ) ) cloned = new Clone ( obj . getTime ( ) )
104
+
105
+ // init cloned regex
106
+ if ( isRegExp ( obj ) ) cloned = new Clone ( obj . source , obj . flags )
94
107
95
- // clone typed array
108
+ // init cloned typed array
96
109
if ( isTypedArray ( obj ) ) {
97
- result = new Clone ( init ( obj . buffer ) , obj . byteOffset , obj . length )
110
+ cloned = new Clone ( dclone ( obj . buffer ) , obj . byteOffset , obj . length )
98
111
}
99
112
100
- // clone unknown value
101
- if ( ! cast < Optional < T > > ( result ) && isFunction ( Clone ) ) result = new Clone ( )
113
+ // init unknown clone
114
+ if ( isUndefined ( cloned ) && isFunction ( Clone ) ) cloned = new Clone ( )
115
+
116
+ // check for circular references and return corresponding clone
117
+ if ( cache . has ( obj ) ) return cast ( cache . get ( obj ) )
118
+
119
+ // cache object and clone
120
+ cache . set ( obj , ( cloned = ifelse ( cloned , cloned , cast ( { } ) ) ) )
121
+
122
+ // define own properties of initial object on cloned object
123
+ for ( const key of properties ( obj ) ) {
124
+ /**
125
+ * Property descriptor for {@linkcode key}.
126
+ *
127
+ * @const {PropertyDescriptor} $d
128
+ */
129
+ const $d : PropertyDescriptor = descriptor ( obj , key )
130
+
131
+ // define property on cloned object
132
+ define (
133
+ cloned ,
134
+ key ,
135
+ ifelse ( $d . value , { ...$d , value : dclone ( $d . value ) } , $d )
136
+ )
137
+ }
102
138
103
- return ifelse ( result , result , cast ( { } ) )
104
- }
139
+ // repopulate map
140
+ if ( isMap ( obj ) && isMap ( cloned ) ) {
141
+ for ( const [ key , val ] of obj . entries ( ) ) cloned . set ( key , dclone ( val ) )
142
+ }
105
143
106
- /**
107
- * Cloned {@linkcode obj}.
108
- *
109
- * @const {T} result
110
- */
111
- const ret : T & object = init ( obj )
112
-
113
- // redefine own properties on cloned object
114
- for ( const key of properties ( obj ) ) {
115
- define ( ret , key , {
116
- ...descriptor ( obj , key ) ,
117
- value : clone ( descriptor ( obj , key ) . value )
118
- } )
119
- }
144
+ // repopulate set
145
+ if ( isSet ( obj ) && isSet ( cloned ) ) for ( const v of obj ) cloned . add ( dclone ( v ) )
120
146
121
- // repopulate map
122
- if ( isMap ( obj ) && isMap ( ret ) ) {
123
- for ( const [ key , val ] of obj . entries ( ) ) ret . set ( key , clone ( val ) )
147
+ return cloned
124
148
}
125
149
126
- // repopulate set
127
- if ( isSet ( obj ) && isSet ( ret ) ) for ( const val of obj ) ret . add ( clone ( val ) )
128
-
129
- return ret
150
+ return dclone ( value )
130
151
}
131
152
132
153
export default clone
0 commit comments