b-studios

Lazy Attributes in ECMAScript 5

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.

Motivation

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.

Improved Version

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().

The ES5 Way – Getters

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).

Off topic: Other property descriptor helpers

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

});

ES6 Arrow Functions

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
  )

});

Comments