Sometimes you need to get “meta”. What if you could tell a variable that any time it changes, it should automatically report that change to a log file, without you having to write code to do it? What about identifying a function should be called by a web server whenever a particular URL pattern is matched? Or maybe, as with tools like mobX, you want to have functions be invoked only when the variables they reference change. These could radically simplify the amount of “communication” code that you need to write, and more can concentrate the responses to such changes in a consistent manner rather than force piecemeal edits. This is the role of decorators, perhaps one of the most important capabilities to emerge from the ES2017 upgrades. This post will give a quick primer on ES2017 Decorators.

Decorators

Decorators are familiar to people who work with Babel, but these (and the associated code constructs that these enable, such as those used by Mobx or similar libraries) have yet to make their way into broad implementation natively in most browser engines. As such, you will need to use Babel as a preprocessor for any of the following (or use the ES2015 implementation, discussed below).

Functions are objects. This single fact opens up an entire world in which functions can be “decorated” in various ways in order to expose certain functionality. A decorator, in this context, is a function that wraps around another function in order to provide information to some other process. Decorators differ from ordinary functions in that they do not ordinarily change the result of the function, but rather invoke some additional action when the functon is called, such as adding an entry in a log or indicating that a given parametric class property is observable or not.

The use of such decorators has been around for awhile, and collectively is known as aspect-programming (or, sometimes, metaprogramming). They are, however increasingly showing up in Javascript typically at the point where classes and associated methods are defined. Starting with ES2016, transpilers such as Babel used the @ symbol to indicate such a decorator.

A (relatively) simple example of a decorator might be something like a @log decorator, which is used to identify when a given method is called in a class, along with the arguments applied to that method.

class Mat { @log add(a, b) { return a + b; } @log subtract(a,b){ return a - b; } } var m = new Mat(); m.add(2,3) m.subtract(2,3)

The two functions add and subtract do exactly what you would expect them to do. However, in both cases these methods have the @log decorator placed on it, which serve to add a log event every time each of these methods is invoked:

Calling "add" at Thu Dec 22 2016 19:02:16 GMT-0800 (Pacific Standard Time) with [2, 3] Result = 5 Calling "subtract" at Thu Dec 22 2016 19:02:16 GMT-0800 (Pacific Standard Time) with [2, 3] Result = -1

The log file gives the name of the method and the parameters being passed, along with the time stamp for when the method was called.

In order to create this particular bit of magic, it’s necessary to define the log decorator previously. This would likely be loaded in via an import of some sort from an established library module.The @log decorator itself is defined as follows:

function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function() { console.log(`Calling "${name}" at ${new Date()} with arguments `,arguments); var output = oldValue.apply(null, arguments); console.log(`Result = `,output); return output; }; return descriptor; }

Here, the log function is passed a target (the specific function object, the name of that function, and a descriptor that provides relevant information about the function, such as its passed parameters). The old value of the descriptor (which is a function) is temporarily cached in a variable, a log description is sent to the console, and the function is then invoked with the arguments metavariable passed in through the context of the original function (arguments is an array-like object that holds the arguments of the initial calling function).

Given that you have the function and its associated arguments (and with some work the binding class or prototype) this can not only get information but can also be used to populate other control structures. As an example, certain libraries such as Mobx make use of decorators to designate @observable variables. When the value of these change, notifications can be passed back to a reference broker object which will then update items that subscribe to that observable “variable”, without having to write code into the setter/getter directly.

This has incredible utility for React and similar libraries, as these will change UI only when observed variables change. Indeed, this is where the true power of decorators come in: the act of invoking methods that can be passed on to specialized objects without the original author of those methods needing to know the internal mechanisms involved.