-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add constructorHandlers
option
#40
✨ Add constructorHandlers
option
#40
Conversation
Can you document this in the README? |
index.js
Outdated
o2[k] = new Map(cloneArray(Array.from(cur), clone)) | ||
} else if (cur instanceof Set) { | ||
o2[k] = new Set(cloneArray(Array.from(cur), clone)) | ||
} else if (constructorHandlers.has(cur.constructor)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (constructorHandlers.has(cur.constructor)) { | |
} else if (cur.constructor !== Object && constructorHandlers.has(cur.constructor)) { |
I think we add a fast path wherever there is a .has()
to bypass the map lookup.
This should allow to avoid the perf regression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have pushed the change. It improves performance, but not back to where it was before:
benchRfdc*100: 212.82ms
benchRfdcProto*100: 229.3ms
benchRfdcCircles*100: 255.588ms
benchRfdcCirclesProto*100: 238.306ms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I've assigned in the if
check to avoid calling both .has()
and .get()
, and benchmarks are improved to:
benchRfdc*100: 203.999ms
benchRfdcProto*100: 215.779ms
benchRfdcCircles*100: 224.12ms
benchRfdcCirclesProto*100: 243.172ms
Which is vaguely in-line with before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Can you rebase your PR? I've fixed CI. |
The motivation of this change is to allow passing custom handlers for particular classes. For example, [`ObjectId`][1]. These can be passed using the new `constructorHandlers` option: ```js const clone = rfdc({ constructorHandlers: [ [ObjectId, (o) => new ObjectId(o)], ], }) ``` Similarly, `RegExp` support can be added manually: ```js const clone = rfdc({ constructorHandlers: [ [RegExp, (o) => new RegExp(o)], ], }) ``` Internally, the special handlers for `Date`, `Map`, and `Set` are moved to use this mechanism to keep code tidy. Limitations ----------- Note that - for performance - this is backed under the hood by a `Map` with the classes as keys, which gives constant-time lookup (compared to eg iterating over an array of handlers). A limitation that this introduces is that subclasses would not be matched, and would need their own handlers, since we don't look up the prototype chain. Performance ----------- Benchmarks before: ``` benchRfdc*100: 206.839ms benchRfdcProto*100: 206.776ms benchRfdcCircles*100: 231.711ms benchRfdcCirclesProto*100: 229.874ms ``` Benchmarks after: ``` benchRfdc*100: 221.126ms benchRfdcProto*100: 239.467ms benchRfdcCircles*100: 241.456ms benchRfdcCirclesProto*100: 257.926ms ``` [1]: davidmarkclements#7
Improves benchmarks to: ``` benchRfdc*100: 212.82ms benchRfdcProto*100: 229.3ms benchRfdcCircles*100: 255.588ms benchRfdcCirclesProto*100: 238.306ms ```
Improves benchmarks to: ``` benchRfdc*100: 203.999ms benchRfdcProto*100: 215.779ms benchRfdcCircles*100: 224.12ms benchRfdcCirclesProto*100: 243.172ms ```
f3b9655
to
f08c7ec
Compare
``` standard: Use JavaScript Standard Style (https://standardjs.com) /home/runner/work/rfdc/rfdc/test/index.js:124:25: Use a regular expression literal instead of the 'RegExp' constructor. (prefer-regex-literals) ```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice addition!
index.js
Outdated
const constructorHandlers = new Map([ | ||
[Date, (o) => new Date(o)], | ||
[Map, (o, fn) => new Map(cloneArray(Array.from(o), fn))], | ||
[Set, (o, fn) => new Set(cloneArray(Array.from(o), fn))] | ||
].concat(opts.constructorHandlers || [])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const constructorHandlers = new Map([ | |
[Date, (o) => new Date(o)], | |
[Map, (o, fn) => new Map(cloneArray(Array.from(o), fn))], | |
[Set, (o, fn) => new Set(cloneArray(Array.from(o), fn))] | |
].concat(opts.constructorHandlers || [])) | |
const constructorHandlers = new Map() | |
constructorHandlers.add(Date, (o) => new Date(o)) | |
constructorHandlers.add(Map, (o, fn) => new Map(cloneArray(Array.from(o), fn))) | |
constructorHandlers.add(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn))) | |
if (opts.constructorHandlers) { | |
opts.constructorHandlers.forEach((name, handler) => constructorHandlers.add(name, handler) | |
} |
It is faster to work around the additional array allocation. That could also be done inside of the Map and Set handlers itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see much difference in the benchmark, but will make the change anyway
benchRfdc*100: 206.686ms
benchRfdcProto*100: 218.722ms
benchRfdcCircles*100: 228.578ms
benchRfdcCirclesProto*100: 239.244ms
Updated benchmark ``` benchRfdc*100: 206.686ms benchRfdcProto*100: 218.722ms benchRfdcCircles*100: 228.578ms benchRfdcCirclesProto*100: 239.244ms ```
Adds TypeScript definitions for davidmarkclements#40
Adds TypeScript definitions for #40
The motivation of this change is to allow passing custom handlers for particular classes. For example,
ObjectId
.These can be passed using the new
constructorHandlers
option:Similarly,
RegExp
support can be added manually:Internally, the special handlers for
Date
,Map
, andSet
are moved to use this mechanism to keep code tidy.Limitations
Note that - for performance - this is backed under the hood by a
Map
with the classes as keys, which gives constant-time lookup (compared to eg iterating over an array of handlers). A limitation that this introduces is that subclasses would not be matched, and would need their own handlers, since we don't look up the prototype chain.Performance
Benchmarks before:
Benchmarks after: