Introduction to Javascript ES7 Decorators and Descriptors

List Of Content

ADS Area (CARBON)

ADS Area (CARBON)

 

 

Getting Started

A Decorators is a function(Object) which adds the functionally and alter the behaviour or other Object which could be a class and Object property or a regular function.

Angular is based on Decorators for identifying the different components and it does some tweaking and custom handling by adding custom methods to the marked class as a component.

Notice that Decorators are still in stage-2 of TC39 EscmaScript standards so it means that there will be a lot of cretical changes to it in the near future.

For working with Decorators we need to use custom Babel plugin with Webpack as the bundler in order to be able to work with decorators in your project, you can grab the working project with the complete configuration from this Github Repo.

Decorators depend on Object Property Descriptors so if you're not familiar with them, we will be discussing them first.

Property Descriptors

A Descriptor is what describes any object property, which tells javascript how to properly manipulate and treat any object which has the following properties.

  • Value: is the actual value of the property whether it is a string, number, boolean or a function reference.
  • Writable: (true by default) whether we can update and change the current property's value or not (read-only).
  • Enumerable: (true by default) it tells if we can enumerate throughout this property or not in an array using loops.
  • Configurable: (true by default) if it is true we will be able to update the current property's descriptor during runtime otherwise the descriptor will be read-only.

So there a couple of important configuration in a property descriptor to simply describe the behaviour of an Object property and how the javascript engine. 

To create an Object property and specify it's descriptor we need to use the static create method under the base Object class.

//We need to provide the base prototype of the object.
var o = Object.create(Object.prototype, {
  a: { value: 1, writable: true, configurable: true },
  b: { value: 2, writable: true }
});

  As we all know, javascript Objects has a prototype chain and the base prototype is the default Object.prototype so we need to provide that otherwise if we specify null the created object won't have base functions that it inherits from the specified prototype.

We provide key/value pairs where the key is the name of the property to be added to the object and its value represents the Descriptor configuration. You will get an Object reference as a return value.

Now to update a property descriptor for an existing Object you can use the Object static methods to manipulate it.

//Change and Update the descriptor of a
//We must specify the Object reference that has the properties.
Object.defineProperty(o, "a", {
  value: 10,
  configurable: true,
  writable: false
});

You can also get the descriptor for a specific property on an Object.

const aDescriptor = Object.getOwnPropertyDescriptor(o, 'a');
//{ value: 1, writable: true, configurable: true, enumurable: true }

Property Decorators

There are two types of decorators:

  • Property Decorator: is what can alter the behaviour of property inside an Object or a Class (property or method).
  • Class Decorator: is the most common type of decorators which allows you to manipulate a class and generate custom constructors.

For property decorator, it is a regular function that takes three arguments the class target, property name and its descriptor and to apply the modification to the assigned property we need to return a valid descriptor for the property we wish to alter.

So it looks something like this.

//Property Decorator
function dec(target, property, descriptor) {
  //Modify the descriptor here...
  return descriptor;
}

So let's say we have a User class.

//Simple user class with constructor that takes first and last names
//And a method for getting the full concatenated name of the user.
class User {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  getFullName() {
    return this.lastName + " " + this.firstName;
  }
}
//We also create an instance of the user and try to log it's name
const user = new User("Brooks", "Alex");
console.log("User: ", user.getFullName());

The output will be something like.

User: Brooks Alex

As we all know by default the getFullName property (method) can be overwritten and updated with a different function anywhere in our code base, so what we want is a way to be able to make the current property read-only and cannot be overwritten what so ever.

//Usually, you can do something like this
User.prototype.getFullName = function() {
  console.log("HACKED!");
};
//This will alter the function definition for every class instance we create 
//with a hacked version

Let's use the help of decorators to make this method read-only and cannot be changed throughout the prototype.

function readonly(target, property, descriptor) {
  //Make it readonly (non-writable) on the descriptor
  descriptor.writable = false;
  return descriptor;
}

Then you can use the read-only decorator on every class method you want to prevent the changing possibility.

...
  @readonly
  getFullName() {
    return this.lastName + " " + this.firstName;
  }
...

If you try to change the method reference during runtime javascript will block the operation and keep the original method definition.

So this is one of the many scenarios you can use the beneficial side of working with Decorators, you still can do the read-only trick without using the help of decorators but this is going to be very painful and may take a lot of lines of code.

Class Decorators

They are the most common type of decorators because they simply do the job perfectly when it comes to manipulating classes and creating custom class constructors.

For a class decorator instead of returning a descriptor in this case we would return a class constructor mainly because classes on javascript are simply a functions that play a role of a constructor with its private scope to run within, before the coming up of ES6 working with classes on javascript was throughout creating regular function that acts as a constructor of the class and wishes assigns it's properties and method to its scope.

Let's work with the same User example that we have seen before but this time we don't need the getFullName method since we'are working with class-based decorators.

class User {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }
}

const user = new User("Brooks", "Alex");

Let's create a decorator that will add a new property to the class by default providing it with custom arguments.

//A Wrapper function to be able to provide custom status argument
function isLoggedIn(status) {
  //Actuall Decorator implementation
  return function(classRef) {
    //The new class Constructor
    return function(firstName, lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
      //Set new property by default on the current class
      this.isLoggedIn = status || false;
    };
  };
}

The isLoggedIn function has three stacks of function the first is a wrapper function simply to be able to provide custom functionality and feed it with custom arguments (status) which returns the actual function implementation of a decorator and lastly it returns the new constructor of the currently assigned class.

The decorator implementation function takes a classRef argument by default provided by the javascript runtime which represents the current class reference so you could do something like an inheritance.

The new constructor it has the same behaviour of the default class constructor of assigning first and last names to the class scope but it adds a new isLoggedIn boolean property by default to the class its value depends on the provided status.

Now just use the decorator with the User class and the new property will be added by default to the class instance.

@isLoggedIn(true)
class User {
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }
}

const user = new User("Brooks", "Alex");
console.log("isLoggedIn: ", user.isLoggedIn); 

You will get either true outputted to the console since we're using the decorator which adds isLoogedIn.

isLoggedIn: true

 There are a lot of different scenarios where you can benefit from using Decorators the right way.

 

 

Share Tutorial

Made With By

Ipenywis Founder, Game/Web Developer, Love Play Games