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.