revision:
JavaScript decorators allow to effectively add brand-new behaviors and features to pre-existing functions or classes. This makes scaling the existing features easier. Decorators wrap around the base code, which adds up to the original functionality of it.
A decorator is a way of wrapping one piece of code with another : literally "decorating" it, which is a concept similar to "functional composition" or "higher-order functions".
This concept is possible in JavaScript because of first-class functions — JavaScript functions that are treated as first-class citizens.
The concept of decorators is not new in JavaScript because higher-order functions are a form of function decorators.
Example
function doSomething(name) { console.log('Hello, ' + name); } function loggingDecorator(wrapped) { return function() { console.log('Starting'); const result = wrapped.apply(this, arguments); console.log('Finished'); return result; } } const wrapped = loggingDecorator(doSomething);
Function decorators are functions. They take a function as an argument and return a new function that enhances the function argument without modifying it.
Decorators use a special syntax in JavaScript, whereby they are prefixed with an "@" symbol and placed immediately "before the code" being decorated.
Example
@log class ExampleClass { doSomething() { // } }
It's possible to use as many decorators on the same piece of code as you desire, and they'll be applied in the order that you declare them.
Also, class decorators come after "export" and "default", and it is possible to decorate both a "class declaration" and "class expression".
When a decorator is called, it receives two arguments : "value" and "context".
The value argument refers to the value being decorated but it is undefined if it is a class field.
The context refers to an object that contains metadata about the value being decorated.
Decorators support classes and public, private, and static class members such as methods, accessors, and class fields.
Example
function decorator (value, context) { console.log("decorated value is:", value); console.log("context is: ", context); } @decorator class C { @decorator // decorates a class field p = 5; @decorator // decorates a method m() {} @decorator // decorates a getter get x() {} @decorator // decorates a setter set x(v) {} }
These are binary functions applied to members of a class. The first argument, "value", refers to the member property of the class we are decorating. This makes possible a pattern where we can optionally return a new method or replace the decorated function. If we return a new method, it will replace the original on the prototype. But if it is a static method, it will replace it on the class itself. However, if we return any other type of value, an error will be thrown.
Example
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("Stefan", 30, "developer"); man.getBio()
class decorators are applied to the whole class, enabling us to decorate the class. The class decorator receives a class as the first argument. And it can optionally return a new class or replace the decorated class, but it throws an error if a nonconstructable value is returned.
Example
function log(value, { kind, name }) { if (kind === "class") { const newClass = class extends value { constructor(...args) { super(...args); console.log(`constructing a class with arguments: ${args.join(", ")}`); } } console.log(`An instance of the ${name} ${kind} has been created`) return newClass; } } @log class Person { constructor(name, profession) { } } const stefan = new Person('Stefan Winters', "Developer");