Series: ESNext

ES2016 Decorators

Published on Feb 05, 2016

Decorators let you easily and repeatedly add functionality to existing functions.

In this episode we go over how to use and create decorators, creating two useful decorators that demonstrate different parts of the API.


Links

Code

There are three booleans on the description argument of a decorator function. Changing them changes how you can interact with the function:

function readOnly(target, name, description) {
  description.writable = false
  return description
}

class Person {
  @readOnly
  name() { return `${this.first} ${this.last}` }
}

const p = new Person()

p.first = 'hello'
p.last = 'world'
// p.name = () => 'hacked' //this can no longer happen
console.log(p.name())

You can use decorators to change how the function itself works, using description.value:

class Person {
  name() { return `${this.first} ${this.last}` }

  @fluent
  sayName(){console.log(this.name())}

  @fluent
  setName(first, last){
    this.first = first;
    this.last = last;
  }
}

function fluent(target, name, description){
  const fn = description.value;

  description.value = function(...args){
    fn.apply(target, args)

    return target
  }
}


const p = new Person()

p.setName('hello', 'world').sayName().setName('goodbye', 'tacos').sayName()
//=> 'hello world'
//=> 'goodbye tacos'

Transcript

Decorators are coming to JavaScript, and they’re going to be awesome. And I say it in the future tense because they’re part of ES7 and they’re not by default part of Babel. However, they are experimentally available. And so you can turn them on in the ‘Try it out’ section of Babel by clicking the ‘Experimental’ check box, and as we’ll see next time when we look at Ember computed decorators, there are ways to turn it on in Ember.

Alright. So let’s explore how decorators work, and we’re going to do that by starting off with the canonical example. So here we have, in the S6 class, and it’s been given the method name. That returns the first name and the last name concatenated together. So we can exercise this by creating a person, and then assigning first and last names to it and then logging out the name. Fairly straightforward example.

However, let’s say we had a problem. There’s someone who had access to this and they kept on overwriting the name function. So as you can see, we’re overwriting the name function and outputting hacked. So we’d want to make this name function readOnly. How do we do this?

So it’s going to end up looking like this. This is a decorator, and what it does you can recognize it by the @ symbol, and it will say alright, we’re going to use the function called readOnly and it’s going to apply to whatever is right after it, in this case the name method. So it’s a method that takes a method and changes it. So readOnly is going to just be a function, a regular function, and it’s going to take a target, a name, and a description. Those are the three arguments. And for now, let’s just log out what those are.

So here we can see that the target is the person class, and then the name is funnily enough name, because that’s what the method is, so it’s the name of the method. And then the description, here it’s this object. It has three Booleans, configurable, enumerable, and writable, as well as a value which happens to be that function, the function that we’ve given it here.

And so what we’re going to want to do to make this readOnly is, here we can change writable. So description.writable = false, and then we’ll just return the description.

What we’ll find here is that now we have the error Cannot assign to read only property 'name' of [object Object], which is the person. And we comment that out, it’s back to normal. So now our code is preventing us from overwriting that method. And it’s doing that because we have the readOnly decorator. If we cross this off, we’ll see that we can be hacked again.

Of course overwriting just one of these Booleans isn’t all you can do. You can also mess with the value, change how the function actually works. We’ll do that. So instead of readOnly, let’s do something fluent. Now we won’t define fluent right away. What we’re going to do is set up a few more methods. They’ll be sayName and setName. So sayName will just say the name, it’ll log it out, and then setName will set the first and last name using the two things that we give it. And both of these will be fluent once we comment those back in.

Now here’s how we use these. So instead of setting these directly, we’ll use setName and we’ll give it hello and world. And then instead of logging it out, we can call sayName, and once again scopes bite us.

Alright, so we’re saying the name, but what if we wanted to do this in a jquery-like fluent syntax? Currently we can’t because it acts as if this is undefined. Our solution to this will be the fluent decorator, which I did steal mostly from this YouTube video, so you can go ahead and watch this afterwards if you like.

So our fluent function will of course take the target, name, and the description, and then we’re going to go ahead and save the function off of descriptor.value, so we’re saving the original function. And then we’re going to overwrite the descriptor.value with a new function.

First we’re going to give it all the arguments, target, name, and description, but we’re going to do something a little different. So we’re going to apply this first, and then we’re just going to return the target. So previously, I believe it was doing fn.apply, but now we’re also returning the target. And so if we set these to fluent, I will rename these and we can see that it’s now working.

So setName will return the person, the target, to here, and so we’re calling sayName on the person, the target. And we can even setName again to ('goodbye' , 'tacos'). You may think that’s sad but they’re going goodbye because we’re eating them, so it’s happy. Alright, and so it says ('hello' , 'world') and it sets the name and says ('goodbye' , 'tacos'). So we have successfully created a fluent decorator.

I think those two examples can give you a fairly good idea of how to get started using decorators. In the next episode, we’ll learn how to use the Ember computed decorators add-on which has some cool decorators already built in. I’ll see you then.

ESNext

Subscribe to our mailing list