JavaScript - tutorial - 28 - decorators

revision:


Content

decorating a function arguments of decorators class member decorators class decorators


decorating a function

top

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".


arguments of decorators

top

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) {}
          }
    

class member decorators

top

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

top

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");