Working on some project I noticed myself using the memoization pattern many times to create lazy evaluated properties. With ES5 a new way of implementing lazy attributes becomes available, preserving the interface of an attribute.
Given the following example object containing a property obj.expensive:
var obj = {
expensive: /* Some really expensive calculations */
}
Our goal is to only evaluate the expression for obj.expensive if it is really necessary.
The most common way to achieve this is using a function and memoization.
var obj = {
expensive: function expensive(){
if (memoized.hasOwnProperty("value")) {
return expensive.value
}
expensive.value = /* Some really expensive calculations */
return expensive.value;
}
}
This way the value is just calculated once at the time obj.expensive is actually used.
This is a lot of boilerplate, so let’s factor most things out into a function lazy.
function lazy(init) {
return function memoized() {
if (memoized.hasOwnProperty("value")) {
return memoized.value
}
memoized.value = init.call(this);
return memoized.value;
}
}
var obj = {
expensive: lazy(function() {
return /* Some really expensive calculations */
})
}
Much better. But still the interface to the user of obj changes from obj.expensive to
obj.expensive().
Using getters the code can be improved even more in order to preserve the interface to the user.
function lazy(init) {
return {
enumerable: true,
configurable: false,
get: function memoized() {
if (memoized.hasOwnProperty("value")) {
return memoized.value
}
memoized.value = init.call(this);
return memoized.value;
}
}
}
var obj = Object.create(Object.prototype, {
expensive: lazy(function() {
return /* Some really expensive calculations */
})
}
You may have noticed, that property descriptors and Object.create are used
to define the property. Since descriptors allow to specify additional meta information on the mutability and visibility of a property I try to use them when ever possible (and appropriate).
Here are just two of the helper functions I usually use to define property descriptors
function method(fun) {
return {
value: fun,
configurable: false,
enumerable: false
};
}
function property(value) {
return {
value: value,
writable: true,
enumerable: true
}
}
Using those an object definition can look like
var o = Object.create(Object.prototype, {
foo: property(42),
expensive: lazy(function() { return /* ... */ }),
doFoo: method(function(arg) {
return this.foo + arg + this.expensive
})
});
This even looks nicer using ES6 Arrow Functions
var o = Object.create(Object.prototype, {
foo: property(42),
expensive: lazy( () => /* ... */),
doFoo: method( (arg) =>
this.foo + arg + this.expensive
)
});