Originally posted on Carbon Five's Blog.
It all comes down to using
defineProperty of the
Object class. Introduced as part of ECMAScript 5 and
major browsers - it allows you to add or modify
a property on an object by not just determining its value but its
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
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
once an instance is created. This is accomplished by using
Object.defineProperty to explicity set the
Now when we instantiate an instance of
Cassette, the string
passed in the construtor is returned by the
More importantly, because we never set the
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:
But wouldn't it be sweeter to do the following?
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
Let's begin by creating an
Assertion "class", that accepts a
value we will be running our assertions against. We will also create and
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
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:
Object.definePropertyallows you add more behavior when a property is assigned or referenced.
- By assigning a value with
Object.definePropertyyou make it read-only.
- Assign a
getfunction to a property to introduce syntactic sugar like pass-throughs and queries for readability.