Heim > Artikel > Web-Frontend > Eine kurze Diskussion über Dekorateure in ES6
In diesem Artikel geht es darum, kurz über Dekorateure in ES6 zu sprechen. Ich hoffe, dass er für Freunde in Not hilfreich ist.
Dekorateur
Dekorator wird hauptsächlich verwendet für:
Dekorationsklasse
Dekorationsmethode oder -attribut
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
class MyClass { @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }
Das können wir machen es in Babel Probieren Sie es auf der offiziellen Website aus, um den von Babel kompilierten Code anzuzeigen.
Wir können uns jedoch auch für die lokale Kompilierung entscheiden:
npm init npm install --save-dev @babel/core @babel/cli npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
Erstellen Sie eine neue .babelrc-Datei
{ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", {"loose": true}] ] }
Kompilieren Sie die angegebene Datei
babel decorator.js --out-file decorator-compiled.js
Vor der Kompilierung:
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
Nach der Kompilierung:
var _class; let MyClass = annotation(_class = class MyClass {}) || _class; function annotation(target) { target.annotated = true; }
Wir können die Dekoration von Klassen sehen, das Prinzip ist:
@decorator class A {} // 等同于 class A {} A = decorator(A) || A;
Vor der Kompilierung:
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; }
Nach der Kompilierung:
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; }
Wir können sehen, dass Babel eine _applyDecoratedDescriptor-Funktion für erstellt hat Dekorieren Sie die Methode.
Bei der Übergabe von Parametern verwenden wir eine Object.getOwnPropertyDescriptor()-Methode:
Object Der getOwnPropertyDescriptor ()-Methode gibt den Eigenschaftsdeskriptor zurück, der einer eigenen Eigenschaft für das angegebene Objekt entspricht. (Eigene Eigenschaften beziehen sich auf Eigenschaften, die dem Objekt direkt zugewiesen sind und nicht in der Prototypenkette nachgeschlagen werden müssen)
Beachten Sie übrigens, dass es sich hierbei um eine ES5-Methode handelt.
Zum Beispiel:
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, // }
Innerhalb der Funktion _applyDecoratedDescriptor erstellen wir zunächst das von Object.getOwnPropertyDescriptor() zurückgegebene Eigenschaftsdeskriptorobjekt. Kopieren:
// 拷贝一份 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; }
Was ist also das Initialisierungsattribut? Das von Object.getOwnPropertyDescriptor() zurückgegebene Objekt verfügt tatsächlich nicht über diese Eigenschaft, die von der Babel-Klasse generiert wird, um beispielsweise mit dem folgenden Code zusammenzuarbeiten:
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 Kompiliert als:
// ... (_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); } })) // ...
Zu diesem Zeitpunkt verfügt der an die Funktion _applyDecoratedDescriptor übergebene Deskriptor über das Initialisierungsattribut.
Der nächste Schritt besteht darin, mehrere Dekoratoren anzuwenden:
/** * 第二部分 * @type {[type]} */ desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);
Für eine Methode werden mehrere Dekoratoren angewendet, wie zum Beispiel:
class MyClass { @unenumerable @readonly method() { } }
Babel wird kompiliert zu:
_applyDecoratedDescriptor( _class.prototype, "method", [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype )
Im zweiten Teil des Quellcodes werden die Operationen reverse() und Reduce() ausgeführt. Daraus können wir auch schließen, dass dieselbe Methode mehrere hat Dekorateure werden von innen nach außen ausgeführt.
/** * 第三部分 * 设置要 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;
Wenn desc ein Initialisierungsattribut hat, bedeutet dies, dass beim Dekorieren des Klassenattributs der Wert von value auf Folgendes gesetzt wird:
desc.initializer.call(context)
Der Wert des Kontexts ist _class.prototype
. Der Grund für call(context)
ist ebenfalls leicht zu verstehen, da es
class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; } }
ist. Letztendlich wird es so sein ausgeführt werden:
Object["define" + "Property"](target, property, desc);
Es ist ersichtlich, dass die Dekorationsmethode im Wesentlichen mit Object.defineProperty()
implementiert wird.
Fügen Sie die Protokollfunktion zu einer Methode hinzu und überprüfen Sie die Eingabeparameter:
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);
Perfekter:
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
Ein Szenario, das wir uns leicht vorstellen können, ist, wenn React Ereignisse bindet:
class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( <button onClick={this.handleClick}> button </button> ); } }
Schreiben wir eine solche Autobind-Funktion:
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) }; }
Manchmal müssen wir die ausgeführte Methode entprellen:
class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log('toggle') } render() { return ( <button onClick={this.handleClick}> button </button> ); } }
Lassen Sie es uns implementieren:
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() } }; } }
Wird zum Zählen der Methodenausführungszeit verwendet:
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); } } } } }
Wird verwendet, um Objektmethoden in die Klasse zu mischen:
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"
Eine einfache Implementierung von mixin ist wie folgt:
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]); } } } }; }
Wenn React in der tatsächlichen Entwicklung in Verbindung mit der Redux-Bibliothek verwendet wird, muss es häufig wie folgt geschrieben werden.
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
Mit dem Decorator können Sie den obigen Code umschreiben.
@connect(mapStateToProps, mapDispatchToProps) export default class MyReactComponent extends React.Component {};
Relativ gesehen scheint die letztere Schreibweise leichter zu verstehen.
Die oben genannten Methoden werden alle zum Ändern der Klassenmethoden verwendet:
const method = descriptor.value;
Aber wenn wir das Instanzattribut der Klasse ändern Aufgrund von Babel kann der Wert nicht über das Wertattribut abgerufen werden. Wir können ihn wie folgt schreiben:
const value = descriptor.initializer && descriptor.initializer();
Das obige ist der detaillierte Inhalt vonEine kurze Diskussion über Dekorateure in ES6. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!