-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: reduce amount of work on initialization
- Simplify approach to wrapping most properties - Reduce the amount of work done when initializing objects
- Loading branch information
1 parent
81ae6c1
commit db4fcbd
Showing
15 changed files
with
206 additions
and
247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,3 @@ | ||
/** | ||
* Walk up the prototype chain and find the property descriptor for the | ||
* given property | ||
* | ||
* @param {object} target | ||
* @param {string} property | ||
* @return {Descriptor|undefined} | ||
*/ | ||
export function getPropertyDescriptor(target, property) { | ||
if (target === undefined) return; | ||
|
||
return ( | ||
Object.getOwnPropertyDescriptor(target, property) || | ||
getPropertyDescriptor(Object.getPrototypeOf(target), property) | ||
); | ||
} | ||
|
||
export function isExtensionOf(childClass, parentClass) { | ||
return childClass.prototype instanceof parentClass; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
function guardBind(fn, ...args) { | ||
if (typeof fn === 'function') { | ||
return fn.bind(...args); | ||
} | ||
} | ||
|
||
class ComputedValidatedProperty { | ||
constructor(desc, klass, originalValue, typeValidators) { | ||
this.isDescriptor = true; | ||
|
||
this.desc = desc; | ||
this.klass = klass; | ||
this.originalValue = originalValue; | ||
this.typeValidators = typeValidators; | ||
|
||
this.setup = guardBind(desc.setup, desc); | ||
this.teardown = guardBind(desc.teardown, desc); | ||
this.willChange = guardBind(desc.willChange, desc); | ||
this.didChange = guardBind(desc.didChange, desc); | ||
this.willWatch = guardBind(desc.willWatch, desc); | ||
this.didUnwatch = guardBind(desc.didUnwatch, desc); | ||
} | ||
|
||
get(obj, keyName) { | ||
let { klass, typeValidators } = this; | ||
let newValue = this.desc.get(obj, keyName); | ||
|
||
if (typeValidators) { | ||
typeValidators.run(klass, keyName, newValue, 'get'); | ||
} | ||
|
||
return newValue; | ||
} | ||
|
||
set(obj, keyName, value) { | ||
let { klass, typeValidators } = this; | ||
let newValue = this.desc.set(obj, keyName, value); | ||
|
||
if (typeValidators) { | ||
typeValidators.run(klass, keyName, newValue, 'set'); | ||
} | ||
|
||
return newValue; | ||
} | ||
} | ||
|
||
export default function wrapComputedProperty( | ||
klass, | ||
instance, | ||
meta, | ||
typeValidators, | ||
key | ||
) { | ||
const possibleDesc = meta.peekDescriptors(key); | ||
|
||
// Handle the argument being overwritten by a computed property | ||
if (possibleDesc && possibleDesc.isDescriptor) { | ||
let originalValue = possibleDesc.get(instance, key); | ||
|
||
let validatedProperty = new ComputedValidatedProperty( | ||
possibleDesc, | ||
klass, | ||
originalValue, | ||
typeValidators | ||
); | ||
|
||
Object.defineProperty(instance, key, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { | ||
return validatedProperty.get(instance, key); | ||
} | ||
}); | ||
|
||
meta.writeDescriptors(key, validatedProperty); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/** | ||
* Transforms a `field` descriptor into a property `getter/setter` that | ||
* ensures that the value matches the validators. | ||
* | ||
* @param {Object} element | ||
* @param {Function} validator | ||
*/ | ||
function wrapFieldDescriptor( | ||
{ descriptor, initializer, key, placement }, | ||
validator | ||
) { | ||
const { ...descriptorToApply } = descriptor; | ||
delete descriptorToApply.writable; | ||
|
||
let initialized = false; | ||
let cachedValue; | ||
|
||
const newElement = { | ||
key, | ||
placement, | ||
kind: 'method', | ||
descriptor: { | ||
...descriptorToApply, | ||
get() { | ||
if (!initialized) { | ||
initialized = true; | ||
cachedValue = initializer ? initializer.call(this) : undefined; | ||
} | ||
|
||
return cachedValue; | ||
}, | ||
set(newValue) { | ||
initialized = true; | ||
|
||
validator.run(this.constructor, key, newValue, 'set'); | ||
|
||
cachedValue = newValue; | ||
} | ||
} | ||
}; | ||
|
||
return newElement; | ||
} | ||
|
||
/** | ||
* Wraps a property `getter/setter` such that the value must match the | ||
* validator. | ||
* | ||
* @param {Object} element | ||
* @param {Function} validator | ||
*/ | ||
function wrapMethodDescriptor({ descriptor, key, ...rest }, validator) { | ||
const { get, set, ...restOfDescriptor } = descriptor; | ||
|
||
const newElement = { | ||
...rest, | ||
key, | ||
descriptor: { | ||
...restOfDescriptor, | ||
get() { | ||
const value = get.call(this); | ||
|
||
validator.run(this.constructor, key, value, 'get'); | ||
|
||
return value; | ||
}, | ||
set(newValue) { | ||
const setterReturnedValue = set.call(this, newValue); | ||
|
||
validator.run(this.constructor, key, this[key], 'set'); | ||
|
||
return setterReturnedValue; | ||
} | ||
} | ||
}; | ||
|
||
return newElement; | ||
} | ||
|
||
/** | ||
* Transforms a `field` descriptor, wrapping it with a setter that performs | ||
* validator. | ||
* | ||
* @param {Object} element | ||
* @param {Function} validator validator function for new values | ||
*/ | ||
export default function wrapDescriptor(element, validator) { | ||
switch (element.kind) { | ||
case 'field': | ||
return wrapFieldDescriptor(element, validator); | ||
case 'method': | ||
return wrapMethodDescriptor(element, validator); | ||
default: | ||
throw new Error( | ||
'`@argument` must be applied to a `field` or property accessor' | ||
); | ||
} | ||
} |
Oops, something went wrong.