Messing Around with JavaScript Decorators

I’ve been looking at the new features in ES6 and boy has JavaScript changed a lot in the last few years. The ECMAScript standards team is really doing a great job making the language more comfortable to work with. My favorite features are block scoped variables with let, a better syntax for defining a class (I never really understood what a “prototype” was), built-in support for loading and exporting modules, and native support for promises. Proxy objects seem like they could be a powerful tool as well. My first impression is that JavaScript is starting to look like a more liberal version of Python, which isn’t a bad thing.

One of the features I looked for in ES6 and could not find was support for function decorators. Decorators can be a great thing to have in a language sometimes. When they fit into an api, they really fit in well and I often use them in my Python library code. I was surprised this feature didn’t make it into ES6 because they are used extensively as part of Angular and React and have native support in TypeScript.

The proposal for decorators can be found in this repository with user guide located here. The proposal is currently at stage two which means it will likely be included in the language in the next major update but are not ready to be included in production code yet. I wrote some sample decorators to test out the new features which you can find in my notes repository here and I’d like to explain to you how they work.

Note: I am not an expert JavaScript, front-end, or Node developer so if you see anything I’m doing wrong in these examples, please let me know in the comments or in an email.

Babel must also be configured with some options in a .babelrc file you can find here.

Now you can compile and run your code with babel like this and it should work correctly:

babel ./index.js -o index-compiled.js && node ./index-compiled.js

Anatomy of a Decorator

A decorator is basically just a function that gets called in the context of a target method or property that is able to change its state somehow. Here is an annotated example of a decorator which does nothing:

function(descriptor){// alter the descriptor to change its properties and return it
descriptor.finisher=function(klass){// now you get the class it was defined on at the end}return descriptor;}class Example {@decorator
decorated(){return'foo';}}

The descriptor that is returned from the decorator is an object that you can mutate to change the target method. The important properties of this object are:

kind – whether this is a ‘method’, a ‘field’, or something else

key – the name of what is being decorated (in this case, ‘decorated’)

descriptor – contains configuration for the property, and the value which you can hook into with custom behavior

finisher – add a function to be called after the class is defined for customization of the class

To have your decorator take parameters, use a function that returns a decorator like the one above.

Example: Reflect a Class

Given a class, we want to find all the methods that are decorated with a certain decorator. We will use the @property decorator for this. This sort of thing would normally be used for a base class in your API that your user is expected to override.

Conclusion

Decorators open up a lot of possibilities in a language and I am looking forward to their inclusion into JavaScript. I plan to use them in a Node library I am writing right now. However, reflection is a very powerful tool and should not be used without careful consideration. Make sure the decorator pattern actually fits your use case before you decide to use them. Have fun with decorators!