Maison  >  Article  >  interface Web  >  Une brève discussion sur les décorateurs dans ES6

Une brève discussion sur les décorateurs dans ES6

不言
不言avant
2018-11-15 17:35:142473parcourir

Le contenu de cet article consiste à parler brièvement des décorateurs dans ES6. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Décorateur

Le décorateur est principalement utilisé pour :

  1. Classe de décoration

  2. Méthode ou attribut de décoration

Classe de décoration

@annotation
class MyClass { }

function annotation(target) {
   target.annotated = true;
}

Méthode ou attribut de décoration

class MyClass {
  @readonly
  method() { }
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

Babel

Installation et compilation

Nous pouvons voir le code compilé de Babel dans Try it out sur le site officiel de Babel.

Mais on peut aussi choisir de compiler localement :

npm init

npm install --save-dev @babel/core @babel/cli

npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties

Créer un nouveau fichier .babelrc

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", {"loose": true}]
  ]
}

Compiler le fichier spécifié

babel decorator.js --out-file decorator-compiled.js

Compilation des classes de décoration

Avant compilation :

@annotation
class MyClass { }

function annotation(target) {
   target.annotated = true;
}

Après compilation :

var _class;

let MyClass = annotation(_class = class MyClass {}) || _class;

function annotation(target) {
  target.annotated = true;
}

On voit que le principe de la décoration des classes est :

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

Compilation des méthodes de décoration

Avant compilation :

class MyClass {
  @unenumerable
  @readonly
  method() { }
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

function unenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}

Après compilation :

var _class;

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) {
    /**
     * 第一部分
     * 拷贝属性
     */
    var desc = {};
    Object["ke" + "ys"](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;
    }

    /**
     * 第二部分
     * 应用多个 decorators
     */
    desc = decorators
        .slice()
        .reverse()
        .reduce(function(desc, decorator) {
            return decorator(target, property, desc) || desc;
        }, desc);

    /**
     * 第三部分
     * 设置要 decorators 的属性
     */
    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["define" + "Property"](target, property, desc);
        desc = null;
    }

    return desc;
}

let MyClass = ((_class = class MyClass {
    method() {}
}),
_applyDecoratedDescriptor(
    _class.prototype,
    "method",
    [readonly],
    Object.getOwnPropertyDescriptor(_class.prototype, "method"),
    _class.prototype
),
_class);

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

Compilation analyse du code source des méthodes de décoration

Nous pouvons voir que Babel a construit une fonction _applyDecoratedDescriptor pour décorer les méthodes.

Object.getOwnPropertyDescriptor()

Lors du passage des paramètres, nous utilisons une méthode Object.getOwnPropertyDescriptor() Jetons un coup d'œil à cette méthode :

Object Le getOwnPropertyDescriptor. () renvoie le descripteur de propriété correspondant à une propre propriété sur l'objet spécifié. (Les propriétés propres font référence aux propriétés qui sont directement affectées à l'objet et n'ont pas besoin d'être recherchées dans la chaîne de prototypes)

Au fait, notez qu'il s'agit d'une méthode ES5.

Par exemple :

const foo = { value: 1 };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
//   value: 1,
//   writable: true
//   enumerable: true,
//   configurable: true,
// }

const foo = { get value() { return 1; } };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
//   get: /*the getter function*/,
//   set: undefined
//   enumerable: true,
//   configurable: true,
// }

La première partie de l'analyse du code source

Dans la fonction _applyDecoratedDescriptor, nous créons d'abord l'objet descripteur de propriété renvoyé par Object.getOwnPropertyDescriptor() J'ai fait une copie :

// 拷贝一份 descriptor
var desc = {};
Object["ke" + "ys"](descriptor).forEach(function(key) {
    desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;

// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setter
if ("value" in desc || desc.initializer) {
    desc.writable = true;
}

Alors, quel est l'attribut d'initialisation ? L'objet renvoyé par Object.getOwnPropertyDescriptor() ne possède pas cette propriété. En effet, il s'agit d'une propriété générée par la Classe Babel afin de coopérer avec le décorateur. Par exemple, pour le code suivant :

class MyClass {
  @readonly
  born = Date.now();
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

var foo = new MyClass();
console.log(foo.born);

. Babel Il sera compilé en :

// ...
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], {
    configurable: true,
    enumerable: true,
    writable: true,
    initializer: function() {
        return Date.now();
    }
}))
// ...

A ce moment, le descripteur passé dans la fonction _applyDecoratedDescriptor a l'attribut initialiseur.

La deuxième partie de l'analyse du code source

L'étape suivante consiste à appliquer plusieurs décorateurs :

/**
 * 第二部分
 * @type {[type]}
 */
desc = decorators
    .slice()
    .reverse()
    .reduce(function(desc, decorator) {
        return decorator(target, property, desc) || desc;
    }, desc);

Pour une méthode, plusieurs décorateurs sont appliqués, tels que :

class MyClass {
  @unenumerable
  @readonly
  method() { }
}

Babel sera compilé en :

_applyDecoratedDescriptor(
    _class.prototype,
    "method",
    [unenumerable, readonly],
    Object.getOwnPropertyDescriptor(_class.prototype, "method"),
    _class.prototype
)

Dans la deuxième partie du code source, les opérations reverse() et reduction() sont également effectuées. constatez que s'il existe plusieurs instances de la même méthode, les décorateurs seront exécutés de l'intérieur vers l'extérieur.

Partie 3 Analyse du code source

/**
 * 第三部分
 * 设置要 decorators 的属性
 */
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["define" + "Property"](target, property, desc);
    desc = null;
}

return desc;

Si desc a un attribut d'initialisation, cela signifie que lorsque l'attribut de classe est décoré, la valeur de value sera définie sur :

desc.initializer.call(context)

Et la valeur du contexte est _class.prototype. La raison de call(context) est également facile à comprendre, car il est possible

class MyClass {
  @readonly
  value = this.getNum() + 1;

  getNum() {
    return 1;
  }
}

En fin de compte, s'il s'agit d'une méthode décorée. ou un attribut, il sera exécuté :

Object["define" + "Property"](target, property, desc);

On voit que la méthode de décoration est essentiellement implémentée à l'aide de Object.defineProperty().

Application

1.log

Ajoutez la fonction log à une méthode et vérifiez les paramètres d'entrée :

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function(...args) {
    console.log(`Calling ${name} with`, args);
    return oldValue.apply(this, args);
  };

  return descriptor;
}

const math = new Math();

// Calling add with [2, 4]
math.add(2, 4);

Plus d'améliorations :

let log = (type) => {
  return (target, name, descriptor) => {
    const method = descriptor.value;
    descriptor.value =  (...args) => {
      console.info(`(${type}) 正在执行: ${name}(${args}) = ?`);
      let ret;
      try {
        ret = method.apply(target, args);
        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);
      } catch (error) {
        console.error(`(${type}) 失败: ${name}(${args}) => ${error}`);
      }
      return ret;
    }
  }
};

2. liaison automatique

class Person {
  @autobind
  getPerson() {
      return this;
  }
}

let person = new Person();
let { getPerson } = person;

getPerson() === person;
// true

Un scénario auquel nous pouvons facilement penser est celui où React lie des événements :

class Toggle extends React.Component {

  @autobind
  handleClick() {
      console.log(this)
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        button
      </button>
    );
  }
}

Écrivons une fonction de liaison automatique comme celle-ci :

const { defineProperty, getPrototypeOf} = Object;

function bind(fn, context) {
  if (fn.bind) {
    return fn.bind(context);
  } else {
    return function __autobind__() {
      return fn.apply(context, arguments);
    };
  }
}

function createDefaultSetter(key) {
  return function set(newValue) {
    Object.defineProperty(this, key, {
      configurable: true,
      writable: true,
      enumerable: true,
      value: newValue
    });

    return newValue;
  };
}

function autobind(target, key, { value: fn, configurable, enumerable }) {
  if (typeof fn !== 'function') {
    throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`);
  }

  const { constructor } = target;

  return {
    configurable,
    enumerable,

    get() {

      /**
       * 使用这种方式相当于替换了这个函数,所以当比如
       * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回
       * 所以这里做了 this 的判断
       */
      if (this === target) {
        return fn;
      }

      const boundFn = bind(fn, this);

      defineProperty(this, key, {
        configurable: true,
        writable: true,
        enumerable: false,
        value: boundFn
      });

      return boundFn;
    },
    set: createDefaultSetter(key)
  };
}

3.debounce

Parfois, nous devons effectuer un traitement anti-shake sur la méthode d'exécution :

class Toggle extends React.Component {

  @debounce(500, true)
  handleClick() {
    console.log('toggle')
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        button
      </button>
    );
  }
}

Implémentons-le :

function _debounce(func, wait, immediate) {

    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

function debounce(wait, immediate) {
  return function handleDescriptor(target, key, descriptor) {
    const callback = descriptor.value;

    if (typeof callback !== 'function') {
      throw new SyntaxError('Only functions can be debounced');
    }

    var fn = _debounce(callback, wait, immediate)

    return {
      ...descriptor,
      value() {
        fn()
      }
    };
  }
}

4.time

Utilisé pour compter le temps d'exécution de la méthode :

function time(prefix) {
  let count = 0;
  return function handleDescriptor(target, key, descriptor) {

    const fn = descriptor.value;

    if (prefix == null) {
      prefix = `${target.constructor.name}.${key}`;
    }

    if (typeof fn !== 'function') {
      throw new SyntaxError(`@time can only be used on functions, not: ${fn}`);
    }

    return {
      ...descriptor,
      value() {
        const label = `${prefix}-${count}`;
        count++;
        console.time(label);

        try {
          return fn.apply(this, arguments);
        } finally {
          console.timeEnd(label);
        }
      }
    }
  }
}

5.mixin

Utilisé pour mélanger les méthodes d'objet dans la classe :

const SingerMixin = {
  sing(sound) {
    alert(sound);
  }
};

const FlyMixin = {
  // All types of property descriptors are supported
  get speed() {},
  fly() {},
  land() {}
};

@mixin(SingerMixin, FlyMixin)
class Bird {
  singMatingCall() {
    this.sing('tweet tweet');
  }
}

var bird = new Bird();
bird.singMatingCall();
// alerts "tweet tweet"

Une implémentation simple de mixin est la suivante :

function mixin(...mixins) {
  return target => {
    if (!mixins.length) {
      throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);
    }

    for (let i = 0, l = mixins.length; i < l; i++) {
      const descs = Object.getOwnPropertyDescriptors(mixins[i]);
      const keys = Object.getOwnPropertyNames(descs);

      for (let j = 0, k = keys.length; j < k; j++) {
        const key = keys[j];

        if (!target.prototype.hasOwnProperty(key)) {
          Object.defineProperty(target.prototype, key, descs[key]);
        }
      }
    }
  };
}

6.redux

Dans le développement réel, lorsque React est utilisé en combinaison avec la bibliothèque Redux, il est souvent nécessaire écrire Voici ce qui suit.

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

Avec le décorateur, vous pouvez réécrire le code ci-dessus.

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

Relativement parlant, cette dernière façon d'écrire semble plus facile à comprendre.

7. Remarque

Les éléments ci-dessus sont tous utilisés pour modifier les méthodes de classe. La façon dont nous obtenons la valeur est :

const method = descriptor.value;

Mais si nous modifions une instance de a. Attribut de classe, à cause de Babel, la valeur ne peut pas être obtenue via l'attribut valeur. Nous pouvons l'écrire comme :

const value = descriptor.initializer && descriptor.initializer();

.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer