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

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

不言
Libérer: 2018-11-15 17:35:14
avant
2539 Les gens l'ont consulté

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;
}
Copier après la connexion
Copier après la connexion

Méthode ou attribut de décoration

class MyClass {
  @readonly
  method() { }
}

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}
Copier après la connexion

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
Copier après la connexion

Créer un nouveau fichier .babelrc

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", {"loose": true}]
  ]
}
Copier après la connexion

Compiler le fichier spécifié

babel decorator.js --out-file decorator-compiled.js
Copier après la connexion

Compilation des classes de décoration

Avant compilation :

@annotation
class MyClass { }

function annotation(target) {
   target.annotated = true;
}
Copier après la connexion
Copier après la connexion

Après compilation :

var _class;

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

function annotation(target) {
  target.annotated = true;
}
Copier après la connexion

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

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;
Copier après la connexion

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;
}
Copier après la connexion

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;
}
Copier après la connexion

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,
// }
Copier après la connexion

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;
}
Copier après la connexion

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);
Copier après la connexion

. Babel Il sera compilé en :

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

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);
Copier après la connexion

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

class MyClass {
  @unenumerable
  @readonly
  method() { }
}
Copier après la connexion

Babel sera compilé en :

_applyDecoratedDescriptor(
    _class.prototype,
    "method",
    [unenumerable, readonly],
    Object.getOwnPropertyDescriptor(_class.prototype, "method"),
    _class.prototype
)
Copier après la connexion

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;
Copier après la connexion

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)
Copier après la connexion

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;
  }
}
Copier après la connexion

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);
Copier après la connexion

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);
Copier après la connexion

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;
    }
  }
};
Copier après la connexion

2. liaison automatique

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

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

getPerson() === person;
// true
Copier après la connexion

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>
    );
  }
}
Copier après la connexion

É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)
  };
}
Copier après la connexion

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>
    );
  }
}
Copier après la connexion

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()
      }
    };
  }
}
Copier après la connexion

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);
        }
      }
    }
  }
}
Copier après la connexion

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"
Copier après la connexion

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]);
        }
      }
    }
  };
}
Copier après la connexion

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);
Copier après la connexion

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

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {};
Copier après la connexion

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;
Copier après la connexion

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();
Copier après la connexion

.

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!

Étiquettes associées:
source:segmentfault.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal