Sweeter Javascript: Defining Properties to Add Syntactic Sugar

Originally posted on Carbon Five’s Blog.

Syntatic sugar makes for more human-readable code and, if done correctly, provides for more flexibility. In the world of Node many turn to Coffeescript to add that “sweetness”, but you can also achieve it with plain old Javascript. TL;DR »

Object.defineProperty

It all comes down to using defineProperty of the Object class. Introduced as part of ECMAScript 5 and implemented in Javascript 1.8.5 - which is supported by Node and most major browsers - it allows you to add or modify a property on an object by not just determining its value but its entire behaviour.

	Object.defineProperty(obj, prop, descriptor)

The magic comes in the descriptor parameter. You can directly set an initial value that will always be returned by anyone using the property. You can use other attributes in the descriptor to make the property writable (allowing new values assigned), configurable (permit further changes), and enumerable (returned during property enumeration).

But you can go beyond the simple retrieval and assignment of value to properties by passing functions as the get and/or set attributes in the descriptor.

Let’s see how we can use it to add our sugar.

An Example: Read-only Attributes

Recently I published nock-vcr, a node module to deliver the same functionaly of the Ruby gem VCR but built atop the HTTP mocking framework nock. VCR use the metaphor of “cassettes” to record the HTTP interactions, with the interactions being written out to a file named after the name given to the cassette. If a cassette doesn’t exist at the time of recording, it will be created.

For various reasons, we don’t want to give users of the module the ability to change the name of a Cassette once an instance is created. This is accomplished by using Object.defineProperty to explicity set the value:

Now when we instantiate an instance of Cassette, the string passed in the construtor is returned by the name property.

More importantly, because we never set the writable attribute in the descriptor to true, no one can assign a new value to the property:

Now Add the Syntactic Sugar

At this point, we haven’t done anything to “sweeten” the syntax. An opportunity arises in implementing exists. If implemented as a function, it would be used as follows:

	if(cassette.exists())

But wouldn’t it be sweeter to do the following?

	if(cassette.exists)

In this case, we pass in a get function when defining ‘exists’ as a property. This function will be run every time the property is referenced, ensuring it represents the current state of the cassette, but without need of keeping tract with another attribute.

Now we can call exists without need of the parenthesis:

A small, “sweet” victory.

Another Example: Sweeter Tests

The previous example did not really gain us much; what are a couple of parenthesis in the larger scheme of things? A more useful example of this technique and the benefits of syntactic sugar comes in writing out a chai-like test framework called rooibos.

Let’s begin by creating an Assertion “class”, that accepts a value we will be running our assertions against. We will also create and export expect function as the only interface into our framework and creating these assertions.

We can add two basic assertions; truthy which will assert if the value is non-falsy and empty, which asserts the value is has no characters if a string, no elements if an array, and is non-null or undefined in all other cases.

At this point, we can use the framework as follows:

We can sweeten this further with to and be properties that pass through the Assertion instance, doing nothing but enhancing readability.

So finally we can write our tests with the sweetest syntax of all:

tl;dr

  • Object.defineProperty allows you add more behavior when a property is assigned or referenced.
  • By assigning a value with Object.defineProperty you make it read-only.
  • Assign a get function to a property to introduce syntactic sugar like pass-throughs and queries for readability.