Comparing version 1.1.4 to 1.2.0
20
index.js
'use strict' | ||
module.exports = rfdc | ||
function copyBuffer (cur) { | ||
if (cur instanceof Buffer) { | ||
return Buffer.from(cur) | ||
} | ||
return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length) | ||
} | ||
function rfdc (opts) { | ||
@@ -20,2 +28,4 @@ opts = opts || {} | ||
a2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
a2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -40,2 +50,4 @@ a2[k] = fn(cur) | ||
o2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
o2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -59,2 +71,4 @@ o2[k] = clone(cur) | ||
o2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
o2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -84,2 +98,4 @@ o2[k] = cloneProto(cur) | ||
a2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
a2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -111,2 +127,4 @@ var index = refs.indexOf(cur) | ||
o2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
o2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -139,2 +157,4 @@ var i = refs.indexOf(cur) | ||
o2[k] = new Date(cur) | ||
} else if (ArrayBuffer.isView(cur)) { | ||
o2[k] = copyBuffer(cur) | ||
} else { | ||
@@ -141,0 +161,0 @@ var i = refs.indexOf(cur) |
{ | ||
"name": "rfdc", | ||
"version": "1.1.4", | ||
"version": "1.2.0", | ||
"description": "Really Fast Deep Clone", | ||
"main": "index.js", | ||
"exports": { | ||
".": "./index.js", | ||
"./default": "./default.js" | ||
}, | ||
"scripts": { | ||
"test": "tap -R min test && npm run lint", | ||
"test": "tap -R min test && npm run lint && tsd", | ||
"bench": "node benchmark", | ||
@@ -43,2 +47,3 @@ "lint": "standard --fix", | ||
"devDependencies": { | ||
"clone-deep": "^4.0.1", | ||
"codecov": "^3.4.0", | ||
@@ -50,3 +55,4 @@ "deep-copy": "^1.4.2", | ||
"standard": "^11.0.1", | ||
"tap": "^12.0.1" | ||
"tap": "^12.0.1", | ||
"tsd": "^0.7.4" | ||
}, | ||
@@ -53,0 +59,0 @@ "directories": { |
@@ -26,3 +26,3 @@ # rfdc | ||
It's marginally faster to allow enumerable properties on the prototype | ||
It's marginally faster to allow enumerable properties on the prototype | ||
to be copied into the cloned object (not onto it's prototype, directly onto the object). | ||
@@ -43,3 +43,3 @@ | ||
additional 25% overhead. Even if an object doesn't have any circular references, | ||
the tracking overhead is the cost. By default if an object with a circular | ||
the tracking overhead is the cost. By default if an object with a circular | ||
reference is passed to `rfdc`, it will throw (similar to how `JSON.stringify` \ | ||
@@ -53,5 +53,14 @@ would throw). | ||
### `default` import | ||
It is also possible to directly import the clone function with all options set | ||
to their default: | ||
```js | ||
const clone = require("rfdc/default") | ||
clone({a: 1, b: {c: 2}}) // => {a: 1, b: {c: 2}} | ||
``` | ||
### Types | ||
`rdfc` clones all JSON types: | ||
`rfdc` clones all JSON types: | ||
@@ -68,2 +77,4 @@ * `Object` | ||
* `undefined` (copied) | ||
* `Buffer` (copied) | ||
* `TypedArray` (copied) | ||
* `Function` (referenced) | ||
@@ -80,13 +91,10 @@ * `AsyncFunction` (referenced) | ||
```js | ||
const rdfc = require('rdfc')() | ||
const rfdc = require('rfdc')() | ||
const err = Error() | ||
err.code = 1 | ||
JSON.parse(JSON.stringify(e)) // {code: 1} | ||
rdfc(e) // {code: 1} | ||
rfdc(e) // {code: 1} | ||
JSON.parse(JSON.stringify(new Uint8Array([1, 2, 3]))) // {'0': 1, '1': 2, '2': 3 } | ||
rdfc(new Uint8Array([1, 2, 3])) // {'0': 1, '1': 2, '2': 3 } | ||
JSON.parse(JSON.stringify({rx: /foo/})) // {rx: {}} | ||
rdfc({rx: /foo/}) // {rx: {}} | ||
rfdc({rx: /foo/}) // {rx: {}} | ||
``` | ||
@@ -101,9 +109,10 @@ | ||
``` | ||
benchDeepCopy*100: 549.618ms | ||
benchLodashCloneDeep*100: 1461.134ms | ||
benchFastCopy*100: 878.146ms | ||
benchRfdc*100: 323.899ms | ||
benchRfdcProto*100: 314.136ms | ||
benchRfdcCircles*100: 384.561ms | ||
benchRfdcCirclesProto*100: 381.775ms | ||
benchDeepCopy*100: 457.568ms | ||
benchLodashCloneDeep*100: 1230.773ms | ||
benchCloneDeep*100: 655.208ms | ||
benchFastCopy*100: 747.017ms | ||
benchRfdc*100: 281.018ms | ||
benchRfdcProto*100: 277.265ms | ||
benchRfdcCircles*100: 328.148ms | ||
benchRfdcCirclesProto*100: 323.004ms | ||
``` | ||
@@ -110,0 +119,0 @@ |
@@ -5,7 +5,10 @@ 'use strict' | ||
const rfdc = require('..') | ||
const cloneDefault = require('../default') | ||
const clone = rfdc() | ||
const cloneProto = rfdc({proto: true}) | ||
const cloneCircles = rfdc({circles: true}) | ||
const cloneCirclesProto = rfdc({circles: true, proto: true}) | ||
const cloneProto = rfdc({ proto: true }) | ||
const cloneCircles = rfdc({ circles: true }) | ||
const cloneCirclesProto = rfdc({ circles: true, proto: true }) | ||
const rnd = (max) => Math.round(Math.random() * max) | ||
types(clone, 'default') | ||
@@ -16,10 +19,17 @@ types(cloneProto, 'proto option') | ||
test('default – does not copy proto properties', async ({is}) => { | ||
is(clone(Object.create({a: 1})).a, undefined, 'value not copied') | ||
test('default – does not copy proto properties', async ({ is }) => { | ||
is(clone(Object.create({ a: 1 })).a, undefined, 'value not copied') | ||
}) | ||
test('proto option – copies enumerable proto properties', async ({is}) => { | ||
is(cloneProto(Object.create({a: 1})).a, 1, 'value copied') | ||
test('default – shorthand import', async ({ same }) => { | ||
same( | ||
clone(Object.create({ a: 1 })), | ||
cloneDefault(Object.create({ a: 1 })), | ||
'import equals clone with default options' | ||
) | ||
}) | ||
test('circles option - circular object', async ({same, is, isNot}) => { | ||
const o = {nest: {a: 1, b: 2}} | ||
test('proto option – copies enumerable proto properties', async ({ is }) => { | ||
is(cloneProto(Object.create({ a: 1 })).a, 1, 'value copied') | ||
}) | ||
test('circles option - circular object', async ({ same, is, isNot }) => { | ||
const o = { nest: { a: 1, b: 2 } } | ||
o.circular = o | ||
@@ -33,4 +43,4 @@ same(cloneCircles(o), o, 'same values') | ||
}) | ||
test('circles option – deep circular object', async ({same, is, isNot}) => { | ||
const o = {nest: {a: 1, b: 2}} | ||
test('circles option – deep circular object', async ({ same, is, isNot }) => { | ||
const o = { nest: { a: 1, b: 2 } } | ||
o.nest.circular = o | ||
@@ -42,12 +52,24 @@ same(cloneCircles(o), o, 'same values') | ||
is(c.nest.circular, c, 'circular references point to copied parent') | ||
isNot(c.nest.circular, o, 'circular references do not point to original parent') | ||
isNot( | ||
c.nest.circular, | ||
o, | ||
'circular references do not point to original parent' | ||
) | ||
}) | ||
test('circles option alone – does not copy proto properties', async ({is}) => { | ||
is(cloneCircles(Object.create({a: 1})).a, undefined, 'value not copied') | ||
test('circles option alone – does not copy proto properties', async ({ | ||
is | ||
}) => { | ||
is(cloneCircles(Object.create({ a: 1 })).a, undefined, 'value not copied') | ||
}) | ||
test('circles and proto option – copies enumerable proto properties', async ({is}) => { | ||
is(cloneCirclesProto(Object.create({a: 1})).a, 1, 'value copied') | ||
test('circles and proto option – copies enumerable proto properties', async ({ | ||
is | ||
}) => { | ||
is(cloneCirclesProto(Object.create({ a: 1 })).a, 1, 'value copied') | ||
}) | ||
test('circles and proto option - circular object', async ({same, is, isNot}) => { | ||
const o = {nest: {a: 1, b: 2}} | ||
test('circles and proto option - circular object', async ({ | ||
same, | ||
is, | ||
isNot | ||
}) => { | ||
const o = { nest: { a: 1, b: 2 } } | ||
o.circular = o | ||
@@ -61,4 +83,8 @@ same(cloneCirclesProto(o), o, 'same values') | ||
}) | ||
test('circles and proto option – deep circular object', async ({same, is, isNot}) => { | ||
const o = {nest: {a: 1, b: 2}} | ||
test('circles and proto option – deep circular object', async ({ | ||
same, | ||
is, | ||
isNot | ||
}) => { | ||
const o = { nest: { a: 1, b: 2 } } | ||
o.nest.circular = o | ||
@@ -70,5 +96,13 @@ same(cloneCirclesProto(o), o, 'same values') | ||
is(c.nest.circular, c, 'circular references point to copied parent') | ||
isNot(c.nest.circular, o, 'circular references do not point to original parent') | ||
isNot( | ||
c.nest.circular, | ||
o, | ||
'circular references do not point to original parent' | ||
) | ||
}) | ||
test('circles and proto option – deep circular array', async ({same, is, isNot}) => { | ||
test('circles and proto option – deep circular array', async ({ | ||
same, | ||
is, | ||
isNot | ||
}) => { | ||
const o = { nest: [1, 2] } | ||
@@ -85,24 +119,24 @@ o.nest.push(o) | ||
function types (clone, label) { | ||
test(label + ' – number', async ({is}) => { | ||
test(label + ' – number', async ({ is }) => { | ||
is(clone(42), 42, 'same value') | ||
}) | ||
test(label + ' – string', async ({is}) => { | ||
test(label + ' – string', async ({ is }) => { | ||
is(clone('str'), 'str', 'same value') | ||
}) | ||
test(label + ' – boolean', async ({is}) => { | ||
test(label + ' – boolean', async ({ is }) => { | ||
is(clone(true), true, 'same value') | ||
}) | ||
test(label + ' – function', async ({is}) => { | ||
test(label + ' – function', async ({ is }) => { | ||
const fn = () => {} | ||
is(clone(fn), fn, 'same function') | ||
}) | ||
test(label + ' – async function', async ({is}) => { | ||
test(label + ' – async function', async ({ is }) => { | ||
const fn = async () => {} | ||
is(clone(fn), fn, 'same function') | ||
}) | ||
test(label + ' – generator function', async ({is}) => { | ||
test(label + ' – generator function', async ({ is }) => { | ||
const fn = function * () {} | ||
is(clone(fn), fn, 'same function') | ||
}) | ||
test(label + ' – date', async ({is, isNot}) => { | ||
test(label + ' – date', async ({ is, isNot }) => { | ||
const date = new Date() | ||
@@ -112,11 +146,11 @@ is(+clone(date), +date, 'same value') | ||
}) | ||
test(label + ' – null', async ({is}) => { | ||
test(label + ' – null', async ({ is }) => { | ||
is(clone(null), null, 'same value') | ||
}) | ||
test(label + ' – shallow object', async ({same, isNot}) => { | ||
const o = {a: 1, b: 2} | ||
test(label + ' – shallow object', async ({ same, isNot }) => { | ||
const o = { a: 1, b: 2 } | ||
same(clone(o), o, 'same values') | ||
isNot(clone(o), o, 'different object') | ||
}) | ||
test(label + ' – shallow array', async ({same, isNot}) => { | ||
test(label + ' – shallow array', async ({ same, isNot }) => { | ||
const o = [1, 2] | ||
@@ -126,4 +160,4 @@ same(clone(o), o, 'same values') | ||
}) | ||
test(label + ' – deep object', async ({same, isNot}) => { | ||
const o = {nest: {a: 1, b: 2}} | ||
test(label + ' – deep object', async ({ same, isNot }) => { | ||
const o = { nest: { a: 1, b: 2 } } | ||
same(clone(o), o, 'same values') | ||
@@ -133,4 +167,4 @@ isNot(clone(o), o, 'different objects') | ||
}) | ||
test(label + ' – deep array', async ({same, isNot}) => { | ||
const o = [ {a: 1, b: 2}, [3] ] | ||
test(label + ' – deep array', async ({ same, isNot }) => { | ||
const o = [{ a: 1, b: 2 }, [3]] | ||
same(clone(o), o, 'same values') | ||
@@ -141,39 +175,39 @@ isNot(clone(o), o, 'different arrays') | ||
}) | ||
test(label + ' – nested number', async ({is}) => { | ||
is(clone({a: 1}).a, 1, 'same value') | ||
test(label + ' – nested number', async ({ is }) => { | ||
is(clone({ a: 1 }).a, 1, 'same value') | ||
}) | ||
test(label + ' – nested string', async ({is}) => { | ||
is(clone({s: 'str'}).s, 'str', 'same value') | ||
test(label + ' – nested string', async ({ is }) => { | ||
is(clone({ s: 'str' }).s, 'str', 'same value') | ||
}) | ||
test(label + ' – nested boolean', async ({is}) => { | ||
is(clone({b: true}).b, true, 'same value') | ||
test(label + ' – nested boolean', async ({ is }) => { | ||
is(clone({ b: true }).b, true, 'same value') | ||
}) | ||
test(label + ' – nested function', async ({is}) => { | ||
test(label + ' – nested function', async ({ is }) => { | ||
const fn = () => {} | ||
is(clone({fn}).fn, fn, 'same function') | ||
is(clone({ fn }).fn, fn, 'same function') | ||
}) | ||
test(label + ' – nested async function', async ({is}) => { | ||
test(label + ' – nested async function', async ({ is }) => { | ||
const fn = async () => {} | ||
is(clone({fn}).fn, fn, 'same function') | ||
is(clone({ fn }).fn, fn, 'same function') | ||
}) | ||
test(label + ' – nested generator function', async ({is}) => { | ||
test(label + ' – nested generator function', async ({ is }) => { | ||
const fn = function * () {} | ||
is(clone({fn}).fn, fn, 'same function') | ||
is(clone({ fn }).fn, fn, 'same function') | ||
}) | ||
test(label + ' – nested date', async ({is, isNot}) => { | ||
test(label + ' – nested date', async ({ is, isNot }) => { | ||
const date = new Date() | ||
is(+clone({d: date}).d, +date, 'same value') | ||
isNot(clone({d: date}).d, date, 'different object') | ||
is(+clone({ d: date }).d, +date, 'same value') | ||
isNot(clone({ d: date }).d, date, 'different object') | ||
}) | ||
test(label + ' – nested date in array', async ({is, isNot}) => { | ||
test(label + ' – nested date in array', async ({ is, isNot }) => { | ||
const date = new Date() | ||
is(+clone({d: [date]}).d[0], +date, 'same value') | ||
isNot(clone({d: [date]}).d[0], date, 'different object') | ||
is(+cloneCircles({d: [date]}).d[0], +date, 'same value') | ||
isNot(cloneCircles({d: [date]}).d, date, 'different object') | ||
is(+clone({ d: [date] }).d[0], +date, 'same value') | ||
isNot(clone({ d: [date] }).d[0], date, 'different object') | ||
is(+cloneCircles({ d: [date] }).d[0], +date, 'same value') | ||
isNot(cloneCircles({ d: [date] }).d, date, 'different object') | ||
}) | ||
test(label + ' – nested null', async ({is}) => { | ||
is(clone({n: null}).n, null, 'same value') | ||
test(label + ' – nested null', async ({ is }) => { | ||
is(clone({ n: null }).n, null, 'same value') | ||
}) | ||
test(label + ' – arguments', async ({isNot, same}) => { | ||
test(label + ' – arguments', async ({ isNot, same }) => { | ||
function fn (...args) { | ||
@@ -185,2 +219,62 @@ same(clone(arguments), args, 'same values') | ||
}) | ||
test(`${label} copies buffers from object correctly`, async ({ ok, is, isNot }) => { | ||
const input = Date.now().toString(36) | ||
const inputBuffer = Buffer.from(input) | ||
const clonedBuffer = clone({ a: inputBuffer }).a | ||
ok(Buffer.isBuffer(clonedBuffer), 'cloned value is buffer') | ||
isNot(clonedBuffer, inputBuffer, 'cloned buffer is not same as input buffer') | ||
is(clonedBuffer.toString(), input, 'cloned buffer content is correct') | ||
}) | ||
test(`${label} copies buffers from arrays correctly`, async ({ ok, is, isNot }) => { | ||
const input = Date.now().toString(36) | ||
const inputBuffer = Buffer.from(input) | ||
const [clonedBuffer] = clone([inputBuffer]) | ||
ok(Buffer.isBuffer(clonedBuffer), 'cloned value is buffer') | ||
isNot(clonedBuffer, inputBuffer, 'cloned buffer is not same as input buffer') | ||
is(clonedBuffer.toString(), input, 'cloned buffer content is correct') | ||
}) | ||
test(`${label} copies TypedArrays from object correctly`, async ({ ok, is, isNot }) => { | ||
const [input1, input2] = [rnd(10), rnd(10)] | ||
var buffer = new ArrayBuffer(8) | ||
const int32View = new Int32Array(buffer) | ||
int32View[0] = input1 | ||
int32View[1] = input2 | ||
const cloned = clone({ a: int32View }).a | ||
ok(cloned instanceof Int32Array, 'cloned value is instance of class') | ||
isNot(cloned, int32View, 'cloned value is not same as input value') | ||
is(cloned[0], input1, 'cloned value content is correct') | ||
is(cloned[1], input2, 'cloned value content is correct') | ||
}) | ||
test(`${label} copies TypedArrays from array correctly`, async ({ ok, is, isNot }) => { | ||
const [input1, input2] = [rnd(10), rnd(10)] | ||
var buffer = new ArrayBuffer(16) | ||
const int32View = new Int32Array(buffer) | ||
int32View[0] = input1 | ||
int32View[1] = input2 | ||
const [cloned] = clone([int32View]) | ||
ok(cloned instanceof Int32Array, 'cloned value is instance of class') | ||
isNot(cloned, int32View, 'cloned value is not same as input value') | ||
is(cloned[0], input1, 'cloned value content is correct') | ||
is(cloned[1], input2, 'cloned value content is correct') | ||
}) | ||
test(`${label} copies complex TypedArrays`, async ({ ok, deepEqual, is, isNot }) => { | ||
const [input1, input2, input3] = [rnd(10), rnd(10), rnd(10)] | ||
var buffer = new ArrayBuffer(4) | ||
const view1 = new Int8Array(buffer, 0, 2) | ||
const view2 = new Int8Array(buffer, 2, 2) | ||
const view3 = new Int8Array(buffer) | ||
view1[0] = input1 | ||
view2[0] = input2 | ||
view3[3] = input3 | ||
const cloned = clone({ view1, view2, view3 }) | ||
ok(cloned.view1 instanceof Int8Array, 'cloned value is instance of class') | ||
ok(cloned.view2 instanceof Int8Array, 'cloned value is instance of class') | ||
ok(cloned.view3 instanceof Int8Array, 'cloned value is instance of class') | ||
isNot(cloned.view1, view1, 'cloned value is not same as input value') | ||
isNot(cloned.view2, view2, 'cloned value is not same as input value') | ||
isNot(cloned.view3, view3, 'cloned value is not same as input value') | ||
deepEqual(Array.from(cloned.view1), [input1, 0], 'cloned value content is correct') | ||
deepEqual(Array.from(cloned.view2), [input2, input3], 'cloned value content is correct') | ||
deepEqual(Array.from(cloned.view3), [input1, 0, input2, input3], 'cloned value content is correct') | ||
}) | ||
} |
21765
9
433
142
9