Skip to content

Instantly share code, notes, and snippets.

@dsherret
Last active March 1, 2022 18:52
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dsherret/cbe661faf7e3cfad8397 to your computer and use it in GitHub Desktop.
Save dsherret/cbe661faf7e3cfad8397 to your computer and use it in GitHub Desktop.
/**
* Caches the return value of get accessors and methods.
*
* Notes:
* - Doesn't really make sense to put this on a method with parameters.
* - Creates an obscure non-enumerable property on the instance to store the memoized value.
* - Could use a WeakMap, but this way has support in old environments.
*/
export function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
if (descriptor.value != null) {
descriptor.value = getNewFunction(descriptor.value);
}
else if (descriptor.get != null) {
descriptor.get = getNewFunction(descriptor.get);
}
else {
throw "Only put a Memoize decorator on a method or get accessor.";
}
}
let counter = 0;
function getNewFunction(originalFunction: () => void) {
const identifier = ++counter;
return function (this: any, ...args: any[]) {
const propName = `__memoized_value_${identifier}`;
let returnedValue: any;
if (this.hasOwnProperty(propName)) {
returnedValue = this[propName];
}
else {
returnedValue = originalFunction.apply(this, args);
Object.defineProperty(this, propName, {
configurable: false,
enumerable: false,
writable: false,
value: returnedValue
});
}
return returnedValue;
};
}
// ------------ ES6 VERSION ----------
export function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>) {
if (descriptor.value != null)
descriptor.value = getNewFunction(descriptor.value);
else if (descriptor.get != null)
descriptor.get = getNewFunction(descriptor.get);
else
throw new Error("Only put a Memoize decorator on a method or get accessor.");
}
const weakMap = new WeakMap<object, Map<string, unknown>>();
let counter = 0;
function getNewFunction(originalFunction: (...args: any[]) => void) {
const identifier = counter++;
function decorator(this: any, ...args: any[]) {
let propertyValues = weakMap.get(this);
if (propertyValues == null) {
propertyValues = new Map<string, unknown>();
weakMap.set(this, propertyValues);
}
let propName = `__memoized_value_${identifier}`;
if (arguments.length > 0)
propName += "_" + JSON.stringify(args);
let returnedValue: any;
if (propertyValues.has(propName))
returnedValue = propertyValues.get(propName);
else {
returnedValue = originalFunction.apply(this, args);
propertyValues.set(propName, returnedValue);
}
return returnedValue;
}
return decorator;
}
@SteveStrong
Copy link

Just wondering, how is this different from a static property

@natew
Copy link

natew commented Jul 25, 2018

@SteveStrong in certain cases where you want to use the prototype this is useful.

@prestongarno
Copy link

prestongarno commented Oct 30, 2018

type hashCode = "hashCode";

/**
 * Indicates that this hashcode function should be memoized (calculated only once)
 * ***This should only be used on READONLY composite data types for predictable results***
 */
function memoized(target: ValueType, propertyKey: hashCode, descriptor: TypedPropertyDescriptor<() => number>) {

  const original = descriptor.get ? descriptor.get() : descriptor.value;

  Object.defineProperty(target.constructor.prototype, 'hashCode', {
    value: function hashCode() {
      if (!this.__hashCode__) {
        this.__hashCode__ = original.call(this);
      }
      return this.__hashCode__;
    }
  });

  descriptor.value = target.constructor.prototype.hashCode;

  return descriptor;
}

I found this to work - no WeakMap needed.

This might not work as well if you want to dynamically memoize things, but I am using it to memoize hashcode computations for immutable ValueTypes with Immutable.js

Edit: I am not sure how well this would work with sub-classes overriding a memoized function who also decorates the overridden function to memoize the calculation, it might loop infinitely?

@smnbbrv
Copy link

smnbbrv commented May 24, 2019

Here is a WeakMap based version of this decorator, that respects single argument (by a reference for non-primitives)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment