Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

As a JavaScript developer, decorators might be a new thing to you. I never used decorators in JavaScript development myself until a few months ago. Whereas decorators have been a standard feature in languages such as Python, C#, and Ruby, JavaScript never had decorators.

In JavaScript, decorators are not yet a standard feature. Decorators are a proposal for extending JavaScript classes and are still in stage 3 of the ECMA TC39 specification phase. Therefore, to use decorators in JavaScript, you have to rely on transpiler tools such as Babel.

That said, it is very likely that decorators are going to be adopted as a standard feature in JavaScript since there’s only one stage remaining before adoption. Therefore, this is the perfect time to learn about decorators. So really, what are decorators in JavaScript?

<img alt="javascript frameworks" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/javascript-frameworks.jpg" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="630" src="data:image/svg xml,” width=”1200″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

To put it simply, decorators are just functions. However, they are not like other functions in JavaScript.

Decorators in JavaScript

A decorator is a function that can be used to wrap around classes or class elements, such as methods, properties, and accessors, to modify or enhance their functionality without changing the underlying code.

Decorators allow you to add or enhance the functionality of a class element, such as a method, without changing the underlying code. For instance, if you have a class method and you want to log the time it is called, rather than modify the method itself, you can use a decorator to add this functionality.

I know this might sound like a very complex thing, but trust me, it is not. Let us look at a code sample, which shows a simple decorator in action. Consider the code below:

class Car {
  wheels = 4

  constructor(make, topSpeed) {
    this.make = make;
    this.topSpeed = topSpeed;
  }

  @log // Applying the log decorator to a method
  drive() {
    console.log("Vroom - The Car is moving")
  }

  stop() {
    console.log("Step on the brakes to stop the car")
  }
}

// A simple decorator
function log(target, propertyKey) {
  console.log(propertyKey);
}

In the code above, we have a class called Car, which has several properties and two methods, namely drive and stop. Below the class definition, we have a decorator function called log.

The decorator takes in two arguments, target, which points to the element that is decorated and propertyKey, which contains information on the decorated element. The decorator then prints out the propertyKey.

We then used this decorator to decorate the drive() method. To apply a decorator function, we use the @ symbol, followed by the name of the decorator function. This is placed directly above the class element we want to decorate.

It is important to note that decorators are executed when a class is defined and not when you create an instance of a class or call an element that is being decorated.

The output of the code above is shown below:

{
  kind: 'method',
  name: 'drive',
  static: false,
  private: false,
  metadata: [Object: null prototype] {},
  addInitializer: [Function (anonymous)],
  access: { get: [Function: d], has: [Function (anonymous)] }
}
<img alt="example-decorator-output" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/example-decorator-output.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="290" src="data:image/svg xml,” width=”730″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

Decorators allow for metaprogramming. That is, writing code that can analyze, modify, or generate other parts of the code at compile time or at runtime.

In JavaScript, decorators can be used to replace, provide access, or initialize the value that is being decorated. Decorators are often used to perform tasks such as logging, route definition, route handling, validation, authentication, and memoization.

Whereas you can use decorators to decorate a standalone function, decorators were primarily designed to work with class declarations. Therefore, developers commonly use decorators with classes, and I’d recommend you do the same.

How To Use Decorators in JavaScript

Since decorators are not yet a standard feature in JavaScript, to use them in your code, you need to rely on a transpiler called Babel. Babel allows you to write JavaScript using the latest features, and it will compile your code to a version of JavaScript that is supported by browsers.

To get started using decorators:

1. Create a new folder and give it any name, for instance jsDecorators. You can do this in the terminal by executing:

mkdir jsDecorators

Then navigate into the directory using:

cd jsDecorators

2. Initialize the project and create a package.json file by executing:

npm init -y

You should get such an output:

<img alt="initialize-project" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/initialize-project.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="498" src="data:image/svg xml,” width=”886″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

3. Install all the Babel presets and plugins you’ll need to work with Decorators by executing the following line in the terminal:

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-decorators

4. Create a babel.config.json by executing:

touch babel.config.json

5. Create an src directory by executing:

mkdir src

This directory will contain all the code that we write ourselves.

Navigate into the src directory using:

cd src

6. Create an index.js file inside the src directory using:

touch index.js

navigate out of the src directory into the jsDecorators directory using:

cd ..

7. Open the project in a code editor by executing:

code .

8. Open the babel.config.json file and paste the following code inside it:

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "version": "2023-05" }]
  ]
}

This is used to specify the version of the proposal decorators plugin that we are going to use in our current project.

9. Open the package.json file and in the script section, replace it with the following lines:

"scripts": {
    "build": "babel src -d dist",
    "start": "node ./build/index.js"
  }

The two scripts created perform the following functions:

"build": "babel src -d dist"

This line creates a script called build. Once executed, it tells npm to execute the Babel command line interface(CLI). The Babel CLI then looks into a directory called src, takes the untranspiled code, and transpiles it. The -d flag is short for –out-dir and it specifies the output directory for the transpiled code. In this case, the transpiled code will be outputted in a directory called dist.

Therefore, to transpile our source code in the src directory, we will simply execute the following line in the terminal:

npm run build

The next script is:

"start": "node ./dist/index.js"

This script will use Node.js to execute our transpiled code that is found in a directory called dist. Therefore, to execute our transpiled code, we will simply run:

npm start

Your package.json file after adding the two scripts should be as shown below:

{
  "name": "jsdecorators",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src -d dist",
    "start": "node ./dist/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.23.0",
    "@babel/core": "^7.23.3",
    "@babel/plugin-proposal-decorators": "^7.23.3",
    "@babel/preset-env": "^7.23.3"
  }
}

10. With all the configuration done, you are now ready to use decorators in JavaScript. To test if everything works fine, paste the following code containing a class and a decorator used earlier in the article. In your code editor, inside the src directory, open the index.js file and paste the following code:

class Car {
  wheels = 4

  constructor(make, topSpeed) {
    this.make = make;
    this.topSpeed = topSpeed;
  }
  
  // Applying the log decorator to a method
  @log
  drive() {
    console.log("Vroom - The Car is moving")
  }

  stop() {
    console.log("Step on the brakes to stop the car")
  }
}

// A simple decorator function called log
function log(target, propertyKey, descriptor) {
  console.log(propertyKey);
}

11. Now open the terminal and make sure you are inside the jsDecorators directory, at the root of your project.

Execute the following to transpile your JavaScript using Babel:

npm run build

Execute the following to run the resulting JavaScript code:

npm start

If successful, you should get such an output:

<img alt="decorator-output" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/decorator-output.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="383" src="data:image/svg xml,” width=”730″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

Since you now know how to use Decorators, let us look at how to create custom decorators that showcase the common use cases of decorators.

Real-World Use Cases for Decorators

As noted earlier, decorators are used for commonly performed tasks such as logging and validation.

Let us look at some of these use cases as we learn how to create custom decorators:

Logging

One of the use cases of decorators is logging. We can use decorators to log out information such as how long a method takes to execute, error messages, the method being executed, or even the result of a method execution.

To see this in action, let us create our custom decorator that logs out the time a method takes to execute. To do this, in the index.js file, below the Car class declaration, paste the following decorator function:

// Decorator function to log out method execution time
function logExecutionTime(target, propertyKey) {
  const originalMethod = target;

  target = function (...args) {
    const startTime = new Date().getTime();
    const result = originalMethod.apply(this, args);
    const endTime = new Date().getTime();
    const executionTime = endTime - startTime;

    console.log(`The method ${propertyKey.name} took ${executionTime} milliseconds to execute.`);
    return result;
  };
  return target;
}

The above decorator calculates the time a method takes to execute by subtracting the end time from the start time.

Now use it to decorate the drive() and stop methods() as shown below:

  @logExecutionTime
  drive() {
    console.log("Vroom - The Car is moving")
  }

  @logExecutionTime
  stop() {
    console.log("Step on the brakes to stop the car")
  }

Now outside the class and the decorator function, create an instance of a class and call the above methods as shown below:

const subaru = new Car('impreza', 240);
subaru.drive();
subaru.stop();

Now execute the file by first building it using npm run build, then executing the resulting code using npm start.

You should get such an output:

<img alt="logging-decorator-output" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/logging-decorator-output.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="302" src="data:image/svg xml,” width=”730″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

In this example, the logExecutionTime decorator was used to log out the time a method takes to execute. You can use this method to decorate any class method and get the time it takes for the method to execute when called.

Validation

Validation involves checking the validity or accuracy of something. This is a common use case for decorators.

For instance, in the Car class, we can add a property called odomoterReading that shows the distance covered by a car and use a decorator to validate that the value assigned to it is always greater than zero. To try this:

Add the property inside the class definition as shown:

 odometerReading = 0;

Create a decorator that will validate that a value is greater than zero. To do this, outside the Car class definition, add the following custom decorator function:

// Decorator to ensure the value assigned is never less than zero
function ensureNonNegative(target, propertyKey) {
  const setter = target;

  target = function(value) {
    if (value < 0) {
      console.error(`Invalid value for ${propertyKey.name}: ${value}. The value should be non-negative.`);
      return;
    }

    setter.call(this, value);
  };

  return target;
}

This decorator takes in a value and validates that the value is greater than zero. To use the decorator, inside the class definition, create a setter and a getter. The setter will be used to set the value of the odometerReading variable; then a getter will be used to retrieve its value. Decorate the setter with the ensureNonNegative decorator function. The code that does this is shown below:

  // Using a decorator for validation
  @ensureNonNegative
  set odometer(distance) {
    this.odometerReading = distance
  }

  get odometerValue() {
    return this.odometerReading
  }

To test it out, outside the class definition and the decorator functions, add the following code:

const subaru = new Car('impreza', 240);
subaru.odometer = -100;
console.log(`The current odomoter reading is ${subaru.odometerValue}`)

This creates an instance of the Car class and uses the setter to try to assign a value of -100. Upon running the code, you get such an output:

<img alt="negative-value-validator" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/negative-value-validator.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="261" src="data:image/svg xml,” width=”725″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

The output shows the decorator ensured a negative value could not be assigned to odometerReading. If you remove the negative sign and execute the code again, you get the following output, showing that a positive value is a valid value for odometerReading.

<img alt="positive-value-decorator" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/positive-value-decorator.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="261" src="data:image/svg xml,” width=”725″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

Difference Between Decorators and Higher-Order Functions

In JavaScript, a higher-order function is a function that can operate on other functions. A higher-order function is a function that takes in one or more functions as an argument and/or returns a function. An example of a higher-order function is shown below:

// Higher-order function that takes a function as an argument
function squareEachElement(arr, transformFunction) {
  return arr.map(transformFunction);
}

// Function passed to the higher-function as an argument
function square(number) {
  return number * number;
}

// Using the higher-order function with the square function
const squaredNumbers = squareEachElement([1,2,3,4], square);
console.log(squaredNumbers)

Output:

<img alt="higher-order-functions" data- data-src="https://kirelos.com/wp-content/uploads/2023/11/echo/higher-order-functions.png" data- data-wp-effect="effects.core.image.setButtonStyles" data-wp-effect–setstylesonresize="effects.core.image.setStylesOnResize" data-wp-init="effects.core.image.initOriginImage" data-wp-on–click="actions.core.image.showLightbox" data-wp-on–load="actions.core.image.handleLoad" decoding="async" height="261" src="data:image/svg xml,” width=”725″>

Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language
Decorators in JavaScript Explained in 5 Minutes or Less javascript programming language

A higher-order function wraps around other functions, enhancing their functionality without modifying the underlying code. But wait, isn’t this exactly what decorators are?

Well, decorators are a form of higher-order functions. However, there is an important distinction between the two. Decorators are specifically designed to work with classes and class elements such as methods, accessors, and properties.

Higher-order functions, on the other hand, are a broader functional programming concept that can be used with functions in general. As much as higher-order functions are powerful, they do not have the mechanism to work with classes and class elements, unlike decorators.

Conclusion

Decorators are an important and powerful tool in JavaScript. Decorators allow for metaprogramming and writing of clean, reusable code. A good number of JavaScript and TypeScript libraries and frameworks use decorators extensively.

As a developer, consider familiarizing yourself with decorators to allow you to use this advanced JavaScript feature to write better, cleaner, and more maintainable code.

explore some best JavaScript Runtime Environments for better code execution.