Skip to content

Instantly share code, notes, and snippets.

@omrilotan
Last active March 9, 2019 19:36
Show Gist options
  • Save omrilotan/3136c824d76478b4d0d15993469f5daf to your computer and use it in GitHub Desktop.
Save omrilotan/3136c824d76478b4d0d15993469f5daf to your computer and use it in GitHub Desktop.
Memoize mixin for Javascript objects
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.
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