A decorator is a class-related syntax used to annotate or modify classes and class methods.

Decorate class

Decorator class – @decorator

// Target is the constructor Foo
function print(target){
    console.log(target)
}

// Before compiling, note that we use '@print' instead of '@print()'.
@print
class Foo(a){}

/ / the compiled
var Foo =
    print(
        (_class = function Foo() {})
    ) || _class;
Copy the code

Since the print function returns undefined, Foo is still Foo here. What happens when our decorator is @print()?

Decorator class – @decorator()

/ / the compiled
var Foo = ((_dec = print()),
_dec(
    (_class = ((_temp = function Foo() {
        _classCallCheck(this, Foo);

        this.a = 1;
        this.b = 2;
    }),
    _temp))
) || _class);
Copy the code

You can see that the print function is executed first, but since print returns undefined, an error occurs when dec() is executed.

From here, you can see that the decorator is executed at compile time, and you can make changes to the prototype in the corresponding function of the decorator.

thinking

What happens if the decorator function returns another function? What are the application scenarios?

/ / before compilation
function print(mixin){
    return function(name){
        this.name = name;
    }
}

@print
class Foo{}

new Foo('Joe')

/ / the compiled
function print(target) {
    return function(name) {
        this.name = name;
    };
}

var Foo =
    print(
        (_class = function Foo() {
            _classCallCheck(this, Foo);
        })
    ) || _class;
var foo = new Foo('Joe');
console.log(foo.name); / / zhang SAN
console.log(foo.hasOwnProperty('name')); // true
Copy the code

As you can see, Foo’s constructor becomes the function returned by print, with a name attribute on the instance.

After looking at the @decorator method, let’s look at the @decorator() method.

function mixin(. listFn){
    return function(target){
        Object.assign(target.prototype, { ...listFn });
    }
}

function print(){
    console.log(this.name)
}

@mixin(print)
class Foo{
    name = 'Joe';
}

var foo = new Foo();
foo.print(); / / zhang SAN
Copy the code

We can see that foo has a print method on its prototype. Take ruan Yifeng’s article for example. With decorators, you can take code like this

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
Copy the code

Change it to code like this

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
Copy the code

Is the latter one easier to understand.

Decorative properties

/ / before compilation
function print(target){
    console.log(target)
}
class Foo{
    @print name;
}
new Foo();

// The compiled part of the code
var Foo = ((_class = ((_temp = function Foo() {
        _initializerDefineProperty(this.'name', _descriptor, this);
    }),
    _temp)),
    (_descriptor = _applyDecoratedDescriptor(_class.prototype, 'name', [print], {
        configurable: true.enumerable: true.writable: true.initializer: null
    })),
    _class);
Copy the code

So you can see that the decorator is passed in as the third argument to the _applyDecoratedDescriptor, so the _applyDecoratedDescriptor is copied inside the property descriptor, The decorator function is then called, and the property descriptor is returned.

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
    // Copy the original attribute descriptor
    var desc = {};
    Object.keys(descriptor).forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !! desc.enumerable; desc.configurable = !! desc.configurable;if ('value' in desc || desc.initializer) {
        desc.writable = true;
    }
    // Execute the decorator function from the inside out
    desc = decorators.slice().reverse().reduce(function(desc, decorator) {
        return decorator(target, property, desc) || desc;
    }, desc);
    // Decorate the method used
    if(context && desc.initializer ! = =void 0) {
        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
        desc.initializer = undefined;
    }
    if (desc.initializer === void 0) {
        Object.defineProperty(target, property, desc);
        desc = null;
    }
    return desc;
}
Copy the code

Adornment method

/ / before compilation
function print() {}
class Foo {
    @print
    getName() {
        return this.name; }}Copy the code

Decorator decorators differ greatly from the decorator properties and the compiled code of the class. First, we pass the stereotype object of the constructor as context to the _applyDecoratedDescriptor function. A second property descriptors are Object. GetOwnPropertyDescriptor method. Third, the default call to _createClass creates a property for the constructor Foo whose property descriptor is non-enumerable by default.

// The compiled part of the code
var Foo = (
    (_class = (function() {
        function Foo() {}
        _createClass(Foo, [
            {
                key: 'getName'.value: function getName() {
                    console.log('getName'); }}]);returnFoo; }) ()),... , _class );function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
}
function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ('value' in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor); }}Copy the code

reference

  • ECMAScript introduction to 6