Decorators in JavaScript
A decorator is a special type of declaration used to modify the behavior of a class, method, property, or parameter. Essentially, it is a function that adds extra functionality to the decorated target using the syntax of an @ symbol followed by the function name.
Basic Concepts of Decorators
- Decorators are currently a proposal in ECMAScript and require tools like Babel for transpilation to be used.
- Decorators are categorized into class decorators, method decorators, property decorators, and parameter decorators.
- Decorators are executed at compile time, not at runtime.
Basic Syntax Structure of Decorators
// Decorator function
function decorator(target, name, descriptor) {
// Decorator logic
}
// Using decorators
@decorator
class MyClass {
@decorator
myMethod() {}
}
Implementation Steps for Class Decorators
- A class decorator receives one parameter: the constructor of the decorated class.
- It can return a new constructor to replace the original class.
- Example: Adding a static property to a class.
function addVersion(constructor) {
constructor.version = '1.0.0';
return constructor;
}
@addVersion
class MyClass {}
console.log(MyClass.version); // Output: 1.0.0
Detailed Implementation of Method Decorators
- Receives three parameters: the prototype of the target class, the method name, and the property descriptor.
- Can modify the behavior of the method, such as adding logging or validation.
- Must return a property descriptor object.
function log(target, name, descriptor) {
const originalMethod = descriptor.value;
// Modify method implementation
descriptor.value = function(...args) {
console.log(`Calling method ${name}, arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${name} execution result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3); // Will output call logs
Application of Property Decorators
- Receives two parameters: the prototype of the target class and the property name.
- Commonly used in metadata programming or property validation.
- Example: Automatically converting a property to uppercase.
function uppercase(target, propertyName) {
let value = target[propertyName];
// Define getter and setter
const getter = () => value;
const setter = (newVal) => {
value = newVal.toUpperCase();
};
// Redefine the property
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@uppercase
name = 'john';
}
const person = new Person();
console.log(person.name); // Output: JOHN
person.name = 'alice';
console.log(person.name); // Output: ALICE
Execution Order of Multiple Decorators
- Decorators are executed sequentially, starting from the one closest to the target.
- Execution order: from top to bottom, from inner to outer.
- Example demonstrating the execution order.
function decoratorA(target, name, descriptor) {
console.log('Executing decorator A');
return descriptor;
}
function decoratorB(target, name, descriptor) {
console.log('Executing decorator B');
return descriptor;
}
class Example {
@decoratorA
@decoratorB
method() {}
}
// Execution order: B first, then A
Practical Application Scenarios
- Automatic
thisbinding: Solvingthiscontext issues in class methods.
function autoBind(target, name, descriptor) {
const originalMethod = descriptor.value;
return {
configurable: true,
enumerable: false,
get() {
// Bind the method to the current instance
const boundFn = originalMethod.bind(this);
Object.defineProperty(this, name, {
value: boundFn,
configurable: true,
writable: true
});
return boundFn;
}
};
}
- Parameter validation decorator.
function validate(type) {
return function(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
if (typeof args[0] !== type) {
throw new Error(`Parameter type error, expected type: ${type}`);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class Validator {
@validate('string')
process(text) {
return text.toUpperCase();
}
}
Decorators provide powerful metaprogramming capabilities in JavaScript, enabling elegant implementation of cross-cutting concerns (such as logging, validation, performance monitoring, etc.), making the code more modular and maintainable.