Maison > Article > interface Web > Une brève discussion sur les décorateurs dans ES6
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 :
Classe de décoration
Méthode ou attribut de décoration
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
class MyClass { @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }
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
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;
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; }
Nous pouvons voir que Babel a construit une fonction _applyDecoratedDescriptor pour décorer les méthodes.
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, // }
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.
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.
/** * 第三部分 * 设置要 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()
.
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; } } };
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) }; }
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() } }; } }
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); } } } } }
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]); } } } }; }
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.
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!