Last active
March 9, 2019 19:36
-
-
Save omrilotan/3136c824d76478b4d0d15993469f5daf to your computer and use it in GitHub Desktop.
Memoize mixin for Javascript objects
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
class Person { | |
constructor(firstName, lastName) { | |
Object.assign(this, {firstName, lastName}); | |
memoize.call(this, 'name', 'greeting', 'greet'); | |
} | |
get name() { | |
return [this.firstName, this.lastName].filter(part => !!part).join(' '); | |
} | |
get greeting() { | |
return this.name ? `Hello, my name is ${this.name}.` : 'I can\'t remember who I am!'; | |
} | |
greet() { | |
console.log(this.greeting); | |
} | |
} | |
const { greet } = new Person('George', 'Bobbins'); | |
greet(); // Hello, my name is George Bobbins. |
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
module.exports = memoize; | |
/** | |
* Key for the memory property | |
* @private | |
* @type {Symbol} | |
*/ | |
const memory = typeof Symbol === 'function' ? Symbol() : '_memory'; | |
/** | |
* @typedef ObjectProperty | |
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty | |
* @type {Object} | |
* @property {boolean} [configurable=true] | |
* @property {boolean} [enumerable=true] | |
* @property {boolean} [writable=true] | |
* @property {any} [value=undefined] | |
* @property {function} [get=undefined] | |
* @property {function} [set=undefined] | |
*/ | |
/** | |
* Get a fresh copy of a memory property descriptor | |
* @private | |
* @return {ObjectProperty} | |
*/ | |
const memoryProperty = () => ({ | |
value: {}, | |
configurable: false, | |
enumerable: false, | |
writable: false, | |
}); | |
/** | |
* OBJECT_PROPERTY_ATTRIBUTES attributes of an ObjectProperty | |
* @type {Array} | |
*/ | |
const OBJECT_PROPERTY_ATTRIBUTES = [ | |
'get', | |
'value', | |
'set', | |
'configurable', | |
'enumerable', | |
'writable', | |
]; | |
/** | |
* Attributes to convert to memoized attributes | |
* @type {Array} | |
*/ | |
const SHOULD_MEMOIZE = [ | |
'get', | |
'value', | |
]; | |
/** | |
* Create memoized instance methods from class attributes | |
* @param {...String} ...attributes | |
* @return {self} | |
* | |
* @example | |
* class Person { | |
* constructor(firstName, lastName) { | |
* Object.assign(this, {firstName, lastName}); | |
* memoize.call(this, 'name', 'greeting', 'greet'); | |
* } | |
* | |
* get name() { | |
* return [this.firstName, this.lastName].filter(part => !!part).join(' '); | |
* } | |
* | |
* get greeting() { | |
* return this.name ? `Hello, my name is ${this.name}.` : 'I can\'t remember who I am!'; | |
* } | |
* | |
* greet() { | |
* console.log(this.greeting); | |
* } | |
* } | |
*/ | |
function memoize(...attributes) { | |
Object.defineProperty( | |
this, | |
memory, | |
memoryProperty() | |
); | |
attributes.forEach( | |
(property) => assignDescriptorProperties.call(this, property) | |
); | |
return this; | |
} | |
/** | |
* Assigns new descriptor properties. Getter and Values will be wrapped in a memoize method | |
* @param {String} property | |
* no return value | |
*/ | |
function assignDescriptorProperties(property) { | |
const descriptor = Object.getOwnPropertyDescriptor(this.constructor.prototype, property); | |
const attributes = OBJECT_PROPERTY_ATTRIBUTES.reduce( | |
(attributes, attribute) => { | |
switch (typeof descriptor[attribute]) { | |
case 'undefined': | |
// do nothing | |
break; | |
case 'function': | |
if (SHOULD_MEMOIZE.includes(attribute)) { | |
attributes[attribute] = () => store.call( | |
this, | |
`${property}$${attribute}`, | |
descriptor[attribute].bind(this) | |
); | |
} else { | |
attributes[attribute] = descriptor[attribute]; | |
} | |
break; | |
default: | |
attributes[attribute] = descriptor[attribute]; | |
break; | |
} | |
return attributes; | |
}, | |
{} | |
); | |
Object.defineProperty( | |
this, | |
property, | |
attributes | |
); | |
} | |
/** | |
* store Remember values of operations by key | |
* @private | |
* @param {String} item Key of item to store | |
* @param {Function} retreive Operation to calculate the value | |
* @return {Any} | |
*/ | |
function store(item, retreive) { | |
return this[memory][item] = this[memory].hasOwnProperty(item) ? this[memory][item] : retreive(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment