Open In App

What are Decorators and How are they used in JavaScript?

Last Updated : 25 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Decorators in JavaScript are a design pattern that enhances an object’s behavior by wrapping it with additional functionality, without altering its original code. It enable modifications like logging or security checks without changing the underlying implementation. It adopted from languages like Python and C#, allows for flexible extension of object functionality in JavaScript applications.

How to Use Decorators in JavaScript?

A decorator is a higher-order function that modifies the behavior of a function, method, or class. Decorators are used for extending the functionality of a function, method, or class without modifying its code.

Syntax

let variable = function(object) {
    object.property = 'characteristic';
}

// Use as decorator
@variable   
class GFG
{ }
console.log(GFG.property);

Example: It is an example of decorators in older JavaScript versions. The `add` function accepts `print` as a parameter, where `print` acts as a decorator appending “is best” to the string “GFG”. By using `add`, we extend and invoke `print`, effectively concatenating the strings.

JavaScript
// Working of Decorators in javascript

// "add" function takes the function as
// a parameter for wrapping function 
// "print" is wrapped 
function add(fn) {
    return function (s) {

        var gg = s + ' is Best';

        // By concatenating we extend
        // the function "add"
        fn(gg);
    }
}

// Decorated function
function print(s) {
    console.log(s);
}

// Calling "add"
var g = add(print);
g('GFG'); 

Output

GFG is Best

Steps to Run Decorators in JavaScript

To run the decorators in JavaScript, we need to configure babel in out projec. Below is a step-by-step guide

Step 1: Initialize a Node App

npm init

Step 2: Install Babel and Required Plugins

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

The updated dependencies after installing above pacakges are:

"devDependencies": {
    "@babel/cli": "^7.25.7",
    "@babel/core": "^7.25.8",
    "@babel/plugin-proposal-decorators": "^7.25.7",
    "@babel/preset-env": "^7.25.8"
}

Step 3: Configure .babelrc file

// Filename - .babelrc
{
    "plugins": [
        ["@babel/plugin-proposal-decorators", { "version": "legacy" }]
    ],
    "presets": ["@babel/preset-env"]
}

Step 4: Define Decorator in index.js file

// Filename - index.js
let variable = function (target) {
    target.property = 'GFG is best';
}

// Decorator
@variable
class GFG { }

// Print in the console
console.log(GFG.property);

Step 5: Compile the code using Babel

npx babel index.js --out-file compiled_output.js

This command will output the compiled file which will look like this:

JavaScript
// Filename - compiled_output.js

"use strict";

function _typeof(o) {
    "@babel/helpers - typeof";
    return (_typeof = "function" == typeof Symbol && 
             "symbol" == typeof Symbol.iterator 
        ? function (o) { return typeof o; } 
        : function (o) {
            return o &&
                "function" == typeof Symbol &&
                o.constructor === Symbol &&
                o !== Symbol.prototype
                ? "symbol"
                : typeof o;
    }), _typeof(o);
}

var _class;

function _defineProperties(e, r) {
    for (var t = 0; t < r.length; t++) {
        
        var o = r[t];
        
        (o.enumerable = o.enumerable || !1), 
        (o.configurable = !0), "value" in o && 
        (o.writable = !0), Object.defineProperty(
            e, _toPropertyKey(o.key), o);
    }
}

function _createClass(e, r, t) {
    return r && _defineProperties(e.prototype, r), t &&
        _defineProperties(e, t), Object.defineProperty(e, "prototype", {
            writable: !1
        }), e;
}

function _toPropertyKey(t) {
    var i = _toPrimitive(t, "string");
    return "symbol" == _typeof(i) ? i : i + "";
}

function _toPrimitive(t, r) {
    if ("object" != _typeof(t) || !t) return t;
    var e = t[Symbol.toPrimitive];
    if (void 0 !== e) {
        var i = e.call(t, r || "default");
        if ("object" != _typeof(i)) return i;
        throw new TypeError("@@toPrimitive must return a primitive value.");
    }
    return ("string" === r ? String : Number)(t);
}

function _classCallCheck(a, n) {
    if (!(a instanceof n))
        throw new TypeError("Cannot call a class as a function");
}

// Filename - index.js
var variable = function variable(target) {
    target.property = "GFG is best";
};

// Decorator
var GFG = variable(
        (_class = /*#__PURE__*/ _createClass(function GFG() {
            _classCallCheck(this, GFG);
        }))
    ) || _class; // Print in the console
    
console.log(GFG.property);

Output
GFG is best

Step 6: Run the output file

node compiled_output.js

The below example illustrates the decorators in JavaScript: 

Example 1: Defining a decorator variable that adds a property to the class GFG, which is then accessed to log “GFG is best” in the console.

JavaScript
let variable = function (target) {
    target.property = 'GFG is best';
}

// Decorator
@variable
class GFG { }

// Print in the console
console.log(GFG.property);

Output

GFG is best

Example 2: Defining a decorator factory variable that sets a class property based on the passed argument, assigning “GFG is Green” to GFG.property and logging it.

JavaScript
let variable = function (color) {
    return function (target) {
        target.property = color;
    }
};

// The value is passed in the decorator 
@variable('GFG is Green')
class GFG { }

console.log(GFG.property);

Output

GFG is Green

Why to uses Decorator ?

  • Decorators allow for decorating code in an efficient and understandable way.
  • Applying the same techniques to different parts of the code or wrapping functions manually can be challenging.
  • In ES6, decorators help resolve these difficulties by providing a clear syntax for wrapping code with functions or additional logic.
  • Decorators offer a standardized approach to applying wrappers around functions or other code blocks.
  • Although JavaScript decorators are not yet directly supported, future versions may include native support for them.

Types of Decorators

Decorators are called by the appropriate details of the item which will be decorated. Decorators are actually functions that return another function. There are two types of decorators are supported now:  

  1. Class member decorators
  2. Class Decorators

Class Member Decorators

These decorators are applied to a single member of a class. This decorator has properties, methods, getters, and setters. This decorator accepts 3 parameters:

  • target: The class to which the member belongs to.
  • name: Name of the class member.
  • descriptor: Description of the member which is the object that is passed to Object.defineProperty.

Example 1: This example defines a method decorator gfg that logs the parameters and result of the add method in the geek class.

JavaScript
// Decorator function 
function gfg(target, name, descriptor) {
    let fn = descriptor.value;

    // Checks if "descriptor.value" is a function
    if (typeof fn == 'function') {
        descriptor.value = function(...args) {
            console.log(`parameters: ${args}`);
            let result = fn.apply(this, args);

            // Print the addition of passed arguments
            console.log(`addition: ${result}`);
                
            return result;
        }
    }
    return descriptor;
}

class geek {
    @gfg
    add(a, b) {
        return a + b;
    }
}

let e = new geek(); 
e.add(100, 200);

Output

parameters: 100, 200
addition: 300

Example 2: This example demonstrates a readonly decorator to make the getColor method of the car class non-writable, preventing it from being overridden.

JavaScript
let readonly = function(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

class car {
    constructor(color) {
        this.color = color;
    }
    
    // Decorator
    @readonly 
    getColor() {
        return this.color;
    }
}

const rCar = new car('car is Black');

// When descriptor.writable = false;
rCar.getColor = function() {
    // When descriptor.writable = true;
    return 'car is not Black'
}

console.log(rCar.getColor());

Output

car is Black

Class Decorators

These decorators are applied to the entire class. These functions are called with a single parameter which is to be decorated. This function is a constructor function. These decorators are not applied to each instance of the class, it only applies to the constructor function. These decorators are less useful than class member decorators. Because we can use a simple function to do everything which can be done by these decorators.  

Example: This example demonstrates using a log decorator to log the parameters passed to the gfg class constructor.

JavaScript
function log() {

    // Decorator function
    return function decorator(Class) {

		// "arrow" function 
        return (...args) => {
            console.log(`Parameters: ${args}`);
            return new Class(...args);
        };
    };
}

// Decorators
@log
class gfg {
    constructor(name, category) {
        this.name = name;
        this.category = category;
    }
}

const e = new gfg('geek', 'code');

// Arguments for Demo: args
console.log(e);

Output

(...args) =>  
    {
      console.log(`Parameters : args`);
      return new Class(...args);
    }

Benefits to use Decorators

  • Decorators allow you to add new functionality to an object without modifying its code. This can be useful for adding behavior such as logging, error handling, or security checks, without changing the core functionality of the object.
  • Decorators can make your code more modular and easier to maintain, as you can add or remove behavior independently of the core functionality of the object.
  • Decorators can be easily composed, meaning that you can apply multiple decorators to the same object to achieve a desired behavior.

Drawbacks of using Decorators

  • Decorators can make your code more complex, as they add an additional layer of abstraction to your code.
  • Decorators can make your code harder to understand, especially if you are using multiple decorators on the same object.
  • Decorators are a relatively new feature of JavaScript, and they are not supported in all environments.
  • Overall, decorators are a useful tool that can help you add new behavior to your code in a flexible and modular way. However, it’s important to consider the potential complexity and readability trade-offs when using decorators in your code.


Similar Reads