Decorators are a new JavaScript feature added to ES7. Those familiar with Typescript should be aware of this feature earlier. Typescript already supports Decorators and provides ES5 support. This article covers Decorators in detail, and I’m sure you’ll find the ease and elegance it brings to programming.

I was a professional before I became a full-time front-end developer. NET programmer, yes. The use of “features” in.NET is very familiar. Enclosing a parentheses around a class, method, or property initializes a property that affects the behavior of the class, method, or property. This is especially useful in AOP programming, and in ORM frameworks, as if by magic. But JavaScript didn’t have this feature at the time. Decorators were first used in TypeScript because we wanted to serialize context information for the entire application and needed a simple way to label the original domain model to indicate whether or not serialization would happen or the behavior control of serialization. Decorators come into their own in this scenario. Later we needed to refactor our state management, switching between mutable class definitions and the use of immutable objects, and using Decorators produced surprising results both in terms of ease of coding and decoupling. I’ve been trying to put together a colloquial document about the use of Decorators that addresses the topic in the simplest way possible, but I haven’t put pen to paper. Inadvertently found an article on the Internet (https://cabbageapps.com/fell-love-js-decorators/), the writing of this article and I’d like to express the content of the right, and bring them here to do editing and adaptation. If you like English, you can click on the link to read the original article.

1.0 Decorator pattern

If we search for “decorators” or “decorators” directly in a search engine, we will see a description of decorator patterns in design patterns in programming-related results.

A more intuitive example is as follows:

The WeaponAccessory is an ornament, they add extra methods and familiarity to the base class. If you don’t understand it, follow me step by step to implement your own decorator and you will understand. The following picture can help you intuitively understand the decorator.

If we think of decorators simply, we can think of them as a wrapper around objects, around methods, around familiarity. When we need to access an object, if we access it through a wrapper around the object, the behavior attached to the wrapper will be triggered. Like a gun with a silencer. The silencer is a decoration, but it is integrated into the original gun, and the silencer is activated when the gun is fired.

This concept is well understood from an object-oriented perspective. So how do we use decorators in JavaScript?

1.1 Begin the journey of Decorators

Decorators are new to ES7, but with Babel and TypesScript, we can now use them, using TypesScript as an example in this article.

First modify the tsconfig.json file and set experimentalDecorators and emitDecoratorMetadata to true.

{
  "compilerOptions": {
    "target": "es2015"."module": "commonjs"."sourceMap": true."emitDecoratorMetadata": true."experimentalDecorators": true
  },
  "exclude": [
    "node_modules"]},Copy the code

We’ll start with the effects, and then we’ll go from there. Take a look at the following code:

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor) :any {
    var oldValue = descriptor.value;

    descriptor.value = function() {
      console.log(`Calling "${propertyKey}" with`.arguments,target);
      let value = oldValue.apply(null[arguments[1].arguments[0]]);

      console.log(`Function is executed`);
      return value + "; This is awesome";
    };

    return descriptor;
  }

  class JSMeetup {
    speaker = "Ruban";
    //@leDecorator
    welcome(arg1, arg2) {
      console.log(`Arguments Received are ${arg1} ${arg2}`);
      return `${arg1} ${arg2}`; }}const meetup = new JSMeetup();

  console.log(meetup.welcome("World"."Hello"));
Copy the code

Run the above code and get the following result:

Let’s change the code to let go of the comment on line 17.

Run the code again and the result is as follows:

Note the output on the left side of the figure above, and the line numbers shown on the right. We are now sure that the behavior of the welcome function changes with the @leDecorator tag, and that the leDecorator function triggers the change. Based on our basic understanding of decorators above, we can think of leDecorator as welcome’s decorator. Decorator and decorator are connected by the @ sign.

At the JavaScript level we already know decorators sensibly; our code decorates a function. In JavaScript, there are four types of decorators:

  • Method Decorator Function Decorator
  • Property Decorators are familiar with Decorators
  • Class Decorator Class Decorator
  • Parameter Decorator Parameter Decorator

Let’s break down one by one! On!

1.2 Function decorators

The first decorator to be broken is the function decorator, and this section is the core of this article, providing insight into the nature of JavaScript Decorators.

By using function decorators, we can control the input and output of functions.

Here is the definition of a function decorator:

MethodDecorator = <T>(target: Object, key: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | Void;
Copy the code

As long as we follow the above definition, we can customize a function decorator with the following three parameters:

  • Target -> The decorated object
  • Key -> The name of the decorated function
  • Descriptor -> Property descriptor of the property to be passed. Through Object. GetOwnPropertyDescriptor () method to view the property descriptors.

More details about property descriptor You can refer to https://www.jianshu.com/p/19529527df80.

In simple terms, property descriptors can be used to configure the return value of a property of an object, the get/set behavior, whether it can be deleted, whether it can be modified, whether it can be enumerated, and so on. To help you understand decorators, let’s look at an intuitive example.

Open the browser console and enter the following code:

var o, d;
var o = { get foo() { return 17; }, bar:17.foobar:function(){return "FooBar"}}; d =Object.getOwnPropertyDescriptor(o, 'foo');
console.log(d);
d = Object.getOwnPropertyDescriptor(o, 'bar');
console.log(d);
d = Object.getOwnPropertyDescriptor(o, 'foobar');
console.log(d);
Copy the code

The results are as follows:

Here we define an Object o, defines the three properties – foo, bar and foobar, through the Object. After getOwnPropertyDescriptor descriptor () for each attribute and print it out. Enumerable, 64x and writable are different, different and different.

  • Value – > The value returned after a literal or function/attribute is evaluated.
  • Enumerable -> can be enumerable in a (for x in obj) loop
  • The property can be configured without additional control
  • Writable -> Whether the property is writable.

Each property or method has its own descriptor, through which we can modify the property’s behavior or return value. Here’s the key:

The essence of decorators is to modify descriptors

It’s time to start writing a decorator.

1.2.1 Method decorator instance

Let’s use the method decorator to modify the input and output of a function.

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor) :any {
    var oldValue = descriptor.value;

    descriptor.value = function() {
      console.log(`Calling "${propertyKey}" with`.arguments,target);
      // Executing the original function interchanging the arguments
      let value = oldValue.apply(null[arguments[1].arguments[0]]);
      //returning a modified value
      return value + "; This is awesome";
    };

    return descriptor;
  }

  class JSMeetup {
    speaker = "Ruban";
    //@leDecorator
    welcome(arg1, arg2) {
      console.log(`Arguments Received are ${arg1}.${arg2}`);
      return `${arg1} ${arg2}`; }}const meetup = new JSMeetup();

  console.log(meetup.welcome("World"."Hello"));
Copy the code

When no decorators are used, the output value is:

Arguments Received are World, Hello
World Hello
Copy the code

With the decorator enabled, the output value is:

Calling "welcome" with { '0': 'World'.'1': 'Hello' } JSMeetup {}
Arguments Received are Hello, World
Hello World; This is awesome
Copy the code

We can see that the method output value is changed. Now look at the method decorator we defined. With parameters, leDecorator gets the name of the calling object, the parameters of the decorated method, and the descriptor of the decorated method at execution time. We first hold the original value of the method descriptor, the Welcome method we defined, in the oldValue variable. Value is then reassigned to descriptor.

In the new function, the original function is first called, the return value is obtained, and then the return value is modified. Finally, the return Descriptor, the new descriptor is applied to the Welcome method, and the body of the integration function has been replaced.

By using decorators, we can wrap the original function and modify the input and output of the method, which means we can apply any desired magic effect to the target method.

Here are a few caveats:

  • Decorators are executed when the class is declared, not when the class is instantiated.
  • The method decorator returns a value
  • Storing the old descriptor and returning a new descriptor is recommended. This is useful in scenarios where multiple descriptors are used.
  • Do not use arrow functions when setting the value of a descriptor.

Now we have completed and understood the first method decorator. Next we come to the school property decorator.

1.3 Attribute decorators

The property decorator is similar to the method decorator. With the property decorator, you can redefine getters, setters, enumerable, and other properties.

Attribute decorators are defined as follows:

PropertyDecorator = (target: Object, key: string) = > void;
Copy the code

The parameters are described as follows:

  • Target: property owner
  • Key: indicates the attribute name

Before using property decorators in detail, let’s take a quick look at the Object.defineProperty method. The object.defineProperty method is usually used to dynamically add or modify attributes to an Object. Here’s an example:

var o = { get foo() { return 17; }, bar:17.foobar:function(){return "FooBar"}};Object.defineProperty(o, 'myProperty', {
get: function () {
return this['myProperty'];
},
set: function (val) {
this['myProperty'] = val;
},
enumerable:true.configurable:true
});
Copy the code

Test the above code in the debug console.

From the result, we see that we dynamically append attributes to the Object using Object.defineProperty. Let’s implement a simple property decorator based on Object.defineProperty.

function realName(target, key: string) :any {
    // property value
    var _val = target[key];

    // property getter
    var getter = function () {
      return "Ragularuban(" + _val + ")";
    };

    // property setter
    var setter = function (newVal) {
      _val = newVal;
    };

    // Create new property with getter and setter
    Object.defineProperty(target, key, {
      get: getter,
      set: setter
    });
  }

  class JSMeetup {
    //@realName
    public myName = "Ruban";
    constructor() {
    }
    greet() {
      return "Hi, I'm " + this.myName; }}const meetup = new JSMeetup();
  console.log(meetup.greet());
  meetup.myName = "Ragul";
  console.log(meetup.greet());
Copy the code

When decorators are not applied, the output is:

Hi, I'm Ruban
Hi, I'm Ragul
Copy the code

After the decorator is enabled, the result is:

Hi, I'm Ragularuban(Ruban)
Hi, I'm Ragularuban(Ragul)
Copy the code

Is it easy? Next comes the Class decorator.

1.4 Class decorator

Class decorators operate on Class constructors to dynamically add and modify class-related properties and methods. Here is the Class decorator definition:

ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction;
Copy the code

The ClassDecorator takes only one argument, the constructor of the Class. The following example code modifies the class’s original speaker property and dynamically adds an extra property.

function AwesomeMeetup<T extends { new(... args: any[]): {} }>(constructor: T) {
    return class extends constructor implements extra {
      speaker: string = "Ragularuban";
      extra = "Tadah!"; }}//@AwesomeMeetup
  class JSMeetup {
    public speaker = "Ruban";
    constructor() {
    }
    greet() {
      return "Hi, I'm " + this.speaker;
    }
  }

  interface extra {
    extra: string;
  }

  const meetup = new JSMeetup() as JSMeetup & extra;
  console.log(meetup.greet());
  console.log(meetup.extra);
Copy the code

The output value without the decorator enabled is:

With decorators enabled, the output is:

It is important to note here that the constructor is called only once.

Now I’m going to look at the last decorator, the parameter decorator.

1.5 Parameter decorator

If you inferred the function of parameter decorators from the decorators described above, it might be to modify parameters, but that is not the case. Parameter decorators are often used to mark special parameters and then read the corresponding tags in the method decorator to perform further operations. Such as:

function logParameter(target: any, key: string, index: number) {
    var metadataKey = `myMetaData`;
    if (Array.isArray(target[metadataKey])) {
      target[metadataKey].push(index);
    }
    else{ target[metadataKey] = [index]; }}function logMethod(target, key: string, descriptor: any) :any {
    var originalMethod = descriptor.value;
    descriptor.value = function (. args: any[]) {

      var metadataKey = `myMetaData`;
      var indices = target[metadataKey];
      console.log('indices', indices);
      for (var i = 0; i < args.length; i++) {

        if(indices.indexOf(i) ! = =- 1) {
          console.log("Found a marked parameter at index" + i);
          args[i] = "Abrakadabra"; }}var result = originalMethod.apply(this, args);
      return result;

    }
    return descriptor;
  }

  class JSMeetup {
    //@logMethod
    public saySomething(something: string, @logParameter somethingElse: string): string {
      return something + ":"+ somethingElse; }}let meetup = new JSMeetup();

  console.log(meetup.saySomething("something"."Something Else"));

Copy the code

In the above code, we define a parameter decorator that places the decorated parameters into a specified array. In the method decorator, look for the marked parameter and do some further processing. Without the decorator enabled, the output is as follows:

With decorators enabled, the output is as follows:

1.6 summary

Now that we’ve learned all about the use of decorators, here’s a summary of the key uses:

  • At the heart of the method decorator is the method descriptor
  • The core of the property decorator is Object.defineProperty
  • The core of the Class decorator is the constructor
  • Parameter decorators are mainly used as markers and should be used in conjunction with method decorators

More front-end good articles, pay attention to the wechat subscription number “dark soul studio”, reply to “QD”

https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md

https://survivejs.com/react/appendices/understanding-decorators/

https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841

https://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-ii https://github.com/arolson101/typescript-decorators