Understanding JavaScript decorators
Tags: javascript, webdev
Published: 2021-07-29 14:56:29

**Written by [Lawrence Eagles] (https://blog.logrocket.com/author/lawrenceoputa/)** ✏️

## Introduction

According to the [Cambridge dictionary](https://dictionary.cambridge.org/dictionary/english/decorate), to decorate something means "to add something to an object or place, especially in order to make it more attractive."

Decorating in programming is simply wrapping one piece of code with another, thereby decorating it. A decorator (also known as a decorator function) can additionally refer to the design pattern that wraps a function with another function to extend its functionality.

This concept is possible in JavaScript because of first-class functions — JavaScript functions that are treated as [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen).

The concept of decorators is not new in JavaScript because higher-order functions are a form of function decorators.

Let’s elaborate on this in the next section.

## Function decorators

Function decorators are functions. They take a function as an argument and return a new function that enhances the function argument without modifying it.

### Higher-order functions

In JavaScript, higher-order functions take a first-class function as an argument and/or return other functions.

Consider the code below:

```javascript
const logger = (message) => console.log(message)

function loggerDecorator (logger) {
    return function (message) {
        logger.call(this, message)
        console.log("message logged at:", new Date().toLocaleString())
    }
}

const decoratedLogger = loggerDecorator(logger);
```

We have decorated the `logger` function by using the `loggerDecorator` function. The returned function — now stored in the `decoratedLogger` variable —  does not modify the `logger` function. Instead, the returned function decorates it with the ability to print the time a message is logged.

Consider the code below:

```javascript
logger("Lawrence logged in: logger") // returns Lawrence logged in: logger

decoratedLogger("Lawrence logged in: decoratedLogger") 
// returns:
// Lawrence logged in: decoratedLogger
// message logged at: 6/20/2021, 9:18:39 PM
```

We see that when the `logger` function is called, it logs the message to the console. But when the `decoratedLogger` function is called, it logs both the message and current time to the console.

Below is another sensible example of a function decorator:

```javascript
//ordinary multiply function
let Multiply = (...args) => {
    return args.reduce((a, b) => a * b)
}

// validated integers
const Validator = (fn) => {
  return function(...args) {
    const validArgs = args.every(arg => Number.isInteger(arg));
    if (!validArgs) {
      throw new TypeError('Argument cannot be a non-integer');
    }
    return fn(...args);
  }
}

//decorated multiply function that only multiplies integers
MultiplyValidArgs = Validator(Multiply);
MultiplyValidArgs(6, 8, 2, 10);
```

In our code above, we have an ordinary `Multiply` function that gives us the product of all its arguments. However, with our `Validator` function — which is a decorator — we extend the functionality of our `Multiply` function to validate its input and multiply only integers.

## Class Decorators

In JavaScript, function decorators exist since the language supports higher-order functions. The pattern used in function decorators cannot easily be used on JavaScript classes. Hence, the [TC39 class decorator proposal](https://tc39.es/proposal-decorators/). You can [learn more about the TC39 process here](https://tc39.es/process-document/).

The TC39 class decorator proposal aims to solve this problem:

```javascript
function log(fn) {
  return function() {
    console.log("Logged at: " + new Date().toLocaleString());
    return fn();
  }
}
class Person {
  constructor(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
  }
  getBio() {
    return `${this.name} is a ${this.age} years old ${this.job}`;
  }
}

// creates a new person
let man = new Person("Lawrence", 20, "developer");

// decorates the getBio method
let decoratedGetBio = log(man.getBio); 
decoratedGetBio(); // TypeError: Cannot read property 'name' of undefined at getBio
```

We tried to decorate the `getBio` method using the function decorator technique, but it does not work. We get a `TypeError` because when the `getBio` method is called inside the `log` function, the `this` variable refers the inner function to the global object.

We can work around this by binding the `this` variable to the `man` instance of the `Person` class as seen below:

```javascript
// decorates the getBio method
let decoratedGetBio = log(man.getBio.bind(man));

decoratedGetBio(); // returns
// Logged at: 6/22/2021, 11:56:57 AM
// Lawrence is a 20 years old developer
```

Although this works, it requires a bit of a hack and a good understanding of the JavaScript `this` variable. So there is a need for a cleaner and easier-to-understand method of using decorators with classes.

Class decorators — or strictly decorators — are a proposal for extending JavaScript classes. [TC39 is currently a stage 2 proposal](https://github.com/tc39/proposals#stage-2), meaning they are expected to be developed and eventually included in the language.

However, with the introduction of ES2015+, and as transpilation has become commonplace, we can use this feature with the help of tools such as [Babel](https://babeljs.io).

Decorators use a special syntax whereby they are prefixed with an `@` symbol and placed immediately above the code being decorated, as seen below:

```javascript
@log
class ExampleClass {
  doSomething() {
    //
  }
}
```

### Types of class decorators

Currently, the types of supported decorators are on classes and members of classes — such as methods, getters, and setters.

Let’s learn more about them below.

#### Class member decorators

A class member decorator is a ternary function applied to members of a class. It has the following parameters:

*   Target — this refers to the class that contains the member property
*   Name — this refers to the name of the member property we are decorating in the class
*   Descriptor — this is the descriptor object with the following properties: value, writable, enumerable, and configurable

The `value` property of the descriptor object refers to the member property of the class we are decorating. This makes possible a pattern where we can replace our decorated function.

Let’s learn about this by rewriting our `log decorator`:

```javascript
function log(target, name, descriptor) {
  if (typeof original === 'function') {
    descriptor.value = function(...args) {
      console.log("Logged at: " + new Date().toLocaleString());
      try {
        const result = original.apply(this, args);
        return result;
      } catch (e) {
        console.log(`Error: ${e}`);
        throw e;
      }
    }
  }
  return descriptor;
}

class Person {
  constructor(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
  }

  @log
  getBio() {
    return `${this.name} is a ${this.age} years old ${this.job}`;
  }
}

// creates a new person
let man = new Person("Lawrence", 20, "developer");

man.getBio()
```

In the code above, we have successfully refactored our `log decorator` — from function decorator pattern to member class decorator.

We simply accessed the member class property — in this case, the `getBio` method — with the `descriptor value`, and replaced it with a new function.

This is cleaner and can be more easily reused than plain higher-order functions.

#### Class decorators

These decorators are applied to the whole class, enabling us to decorate the class.

The class decorator function is a unary function that takes the constructor function being decorated as an argument.

Consider the code below:

```javascript
function log(target) {
  console.log("target is:", target,);
  return (...args) => {
    console.log(args);
    return new target(...args);
  };
}

@log
class Person {
  constructor(name, profession) {
  }
}

const lawrence = new Person('Lawrence Eagles', "Developer");
console.log(lawrence);

// returns
// target is: [Function: Person]
// [ 'Lawrence Eagles', 'Developer' ]
// Person {}
```

In our small, contrived example, we log the `target` argument — the constructor function — and the provided arguments before returning an instance of the class constructed with these arguments.

## Why decorators?

Decorators enable us to write cleaner code by providing an efficient and understandable way of wrapping one piece of code with another. It also provides a clean syntax for applying this wrapper.

This syntax makes our code less distracting because it separates the feature-enhancing code away from the core function. And it enables us to add new features without increasing our code complexity.

Additionally, decorators help us extend the same functionality to several functions and classes, thereby enabling us to write code that is easier to debug and maintain.

While decorators already exist in JavaScript as [higher-order functions](https://blog.logrocket.com/a-closer-look-at-javascript-closures-higher-order-functions-and-currying/), it is difficult or even impossible to implement this technique in classes. Hence, the special syntax TC39 offers is for easy usage with classes.

## Conclusion

Although decorators are a stage 2 proposal, they are already popular in the JavaScript world — thanks to Angular and [TypeScript](https://blog.logrocket.com/a-practical-guide-to-typescript-decorators/).

From this article, we can see that they foster code reusability, thereby keeping our code DRY.

As we wait for decorators to be officially available in JavaScript, you can start using them by using Babel. And I believe you have learned enough in this article to give decorators a try in your next project.

---

## [LogRocket] (https://logrocket.com/signup/): Debug JavaScript errors easier by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

[LogRocket] (https://logrocket.com/signup/) allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

[![LogRocket Dashboard Free Trial Banner] (https://blog.logrocket.com/wp-content/uploads/2020/06/reproduce-javascript-errors.gif)](https://logrocket.com/signup/)

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

[Try it for free] (https://logrocket.com/signup/).