true-clone
v1.0.0
Published
A deep cloning algorithm aware of prototypes, getters/setters, etc.
Downloads
54
Maintainers
Readme
js-true-clone
The goal of this package is to get as close as possible to a perfect JS value clone.
Usage
npm i true-clone
then
const { clone } = require('true-clone');
// later ...
const cloned = clone(myObject);
Behaviour
The cloning algorithm is pretty smart and is aware of:
- Native JS types! This includes primitives,
Array
,Set
,Map
, boxed primitives, typed arrays, etc. - Prototypes! Finally, you can clone custom classes!
- Getters! These will be replicated on the result as getters, not as the computed value.
- Setters! These will be replicated on the result.
- Custom properties on native types! For instance:
const ar = []; ar.my = 'prop'; console.assert(clone(ar).my === 'prop')
. - (Non-)enumerability, (non-)configurability, and/or (non-)writability of object properties! These will be respected.
- etc.
Details
- Mostly works as one would expect!
- However, the following may be notable:
- Prototypes: are referenced rather than copied;
Object.is(clone(Object.create(someProto)).prototype, someProto)
Proxy
objects: do not return other proxies. Additonally, all traps are ignored besides the following:getPrototypeOf
: given prototype is assigned to new objectownKeys
: these are the keys that will appear on the clonegetOwnPropertyDescriptor
: is used to define properties on the clone
- Due to JS limitations, objects of the type
Function
,WeakSet
, andWeakMap
will not be cloned and will instead be returned as-is.
Comparison
Suite in tests.js
run on different packages using node v14.2.0. See compare.sh
.
| package \ feature | primitives | native types | prototypes | monkeypatching | relations | rich properites |
| ----------------------------- | ---------- | ------------ | ---------- | -------------- | ----------- | --------------- |
| true-clone
1.0.0 | | | | | | |
| clone
2.1.2 | | 1 | | 1 | | |
| lodash.clonedeep
4.5.0 | | 2 | | 3 | 4 | |
| rfdc
1.1.4 | | 5 | | | 6 | |
: all tests passing; : no tests passing; : some tests passing
- primitives: supports primitive values
- native types: supports certain native types such as
Array
andSet
- prototypes: supports objects with prototypes
- monkeypatching: copies over monkeypatched attributes
- e.g.
const ar = []; ar.my = 'prop'; console.assert(clone(ar).my === 'prop')
- e.g.
- relations: preserves relational identity, such as in cyclic and diamond-shaped structures
- cyclic e.g. e.g.
const ar = []; ar.push(ar);
- diamonds e.g.
const child = { i_am: 'child' }; const parent = { child_a: child, child_b: child };
- cyclic e.g. e.g.
- rich properties: getters and setters etc.
1
: fails forNumber
,String
,ArrayBuffer
,DataView
, errors types, and typed arrays.2
: fails for sparse arrays,BigInt64Array
,BigUint64Array
, and error types3
: fails forArray
,BigInt64Array
,BigUint64Array
, and error types4
: fails for cyclicMap
andSet
objects5
: fails forNumber
,String
,Boolean
,RegExp
,Map
,Set
,ArrayBuffer
,DataView
, typed arrays, and error types.6
: fails for diamond shapes and cyclic non-Object
values
Benchmarks
true-clone
pays for its correctness with speed.
Benchmark is run on my personal machine; they should be considered only in relation to each other.
See benchmark.js
.
| package \ scope | primitives | native object types | plain objects | arrays |
| ----------------------------- | -------------- | ------------------- | ------------- | ------ |
| true-clone
1.0.0 | 2.300m [ops/s] | 343k | 440k | 1.219m |
| clone
2.1.2 | 1.823m | 96k | 261k | 263k |
| lodash.clonedeep
4.5.0 | 5.791m | 219k | 734k | 1.988m |
| rfdc
1.1.4 | 32.823m | 964k | 2.420m | 2.346m |
- primitives: primitive objects; test case
primitive
- native object types:
Array
,Map
,Set
, andBoolean
; test caseobj types
- plain objects: JSON-able object; test case
Object :: plain small
- arrays: small, dense, non-monkeypatched arrays of primitive values; test case
Array :: pure hom dense_ small