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
)
});