Decorators in JavaScript

Ramkumar Khubchandani
3 min readMar 23, 2024

--

Decorators are a way to modify or enhance the behavior of classes, methods, properties, and even function parameters. They are essentially functions that take a target object (class, method, property, or parameter) and optionally a descriptor object, and return a new descriptor object with modified behavior.

Here’s a step-by-step explanation of decorators in JavaScript:

Step 1: Enable Decorators First, you need to enable decorators in your code. Decorators are currently a stage 2 proposal in the ECMAScript standard, so you need to enable them in your development environment or use a transpiler like Babel.

For example, if you’re using Babel, you need to install the @babel/plugin-proposal-decorators plugin and configure it in your .babelrc file:

{
"plugins": ["@babel/plugin-proposal-decorators"]
}

Step 2: Define a Decorator Function A decorator function is just a regular function that takes a target object and an optional descriptor object as arguments, and returns a new descriptor object.

Here’s an example of a simple decorator function that logs a message when a method is called:

function logMethodCall(target, name, descriptor) {
const original = descriptor.value;

descriptor.value = function(...args) {
console.log(`Method ${name} has been called with arguments: ${args}`);
return original.apply(this, args);
};

return descriptor;
}

Step 3: Apply the Decorator To apply a decorator, you use the @ syntax before the target object (class, method, property, or parameter).

Here’s an example of how to apply the logMethodCall decorator to a class method:

class Calculator {
@logMethodCall
add(a, b) {
return a + b;
}
}

const calculator = new Calculator();
calculator.add(2, 3); // Output: Method add has been called with arguments: 2,3

In this example, the logMethodCall decorator is applied to the add method of the Calculator class. When the add method is called, the decorator function is executed, logging the method name and arguments to the console, and then the original method is executed.

Step 4: Decorator Composition Decorators can be composed by applying multiple decorators to the same target object. The decorators are applied from bottom to top, with the outermost decorator being applied first.

Here’s an example of composing two decorators:

function logMethodCall(target, name, descriptor) {
// ...
}

function checkAccess(target, name, descriptor) {
const original = descriptor.value;

descriptor.value = function(...args) {
if (!this.isAllowed) {
throw new Error('Access denied');
}
return original.apply(this, args);
};

return descriptor;
}

class Calculator {
isAllowed = false;

@logMethodCall
@checkAccess
add(a, b) {
return a + b;
}
}

const calculator = new Calculator();
calculator.add(2, 3); // Error: Access denied

In this example, the checkAccess decorator is applied first, followed by the logMethodCall decorator. The checkAccess decorator checks if the isAllowed property is true before calling the original method. If isAllowed is false, an error is thrown.

Step 5: Decorators for Classes and Properties Decorators can also be applied to classes and properties. Here’s an example of a decorator for a class:

function logConstructor(target) {
return class extends target {
constructor(...args) {
super(...args);
console.log(`New instance of ${target.name} created`);
}
};
}

@logConstructor
class Person {
constructor(name) {
this.name = name;
}
}

const person = new Person('John'); // Output: New instance of Person created

In this example, the logConstructor decorator is applied to the Person class. When a new instance of Person is created, the decorator logs a message to the console.

Here’s an example of a decorator for a property:

function logProperty(target, name) {
let value;

Object.defineProperty(target, name, {
get() {
console.log(`Getting property ${name}`);
return value;
},
set(newValue) {
console.log(`Setting property ${name} to ${newValue}`);
value = newValue;
},
});
}

class Person {
@logProperty
name;
}

const person = new Person();
person.name = 'John'; // Output: Setting property name to John
console.log(person.name); // Output: Getting property name

In this example, the logProperty decorator is applied to the name property of the Person class. The decorator defines a getter and setter for the property that logs a message to the console when the property is accessed or modified.

These are just a few examples of how decorators can be used in JavaScript. Decorators provide a way to modify or enhance the behavior of classes, methods, properties, and parameters in a declarative and reusable way, making the code more modular and easier to maintain.

Want to learn more about such things ?

Follow me or message me on Linkedin.

--

--

Ramkumar Khubchandani
Ramkumar Khubchandani

Written by Ramkumar Khubchandani

Frontend Developer|Technical Content Writer|React|Angular|React-Native|Corporate Trainer|JavaScript|Trainer|Teacher| Mobile: 7709330265|ramkumarkhub@gmail.com

No responses yet