Heim > Web-Frontend > js-Tutorial > Hauptteil

Eine kurze Diskussion über Dekorateure in ES6

不言
Freigeben: 2018-11-15 17:35:14
nach vorne
2493 Leute haben es durchsucht

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:

  1. Dekorationsklasse

  2. Dekorationsmethode oder -attribut

Dekorationsklasse

@annotation
class MyClass { }

function annotation(target) {
   target.annotated = true;
}
Nach dem Login kopieren
Nach dem Login kopieren

Dekorationsmethode oder -attribut

class MyClass {
  @readonly
  method() { }
}

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

Babel

Installation und Kompilierung

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
Nach dem Login kopieren

Erstellen Sie eine neue .babelrc-Datei

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

Kompilieren Sie die angegebene Datei

babel decorator.js --out-file decorator-compiled.js
Nach dem Login kopieren

Kompilieren Sie die Dekoration Klasse

Vor der Kompilierung:

@annotation
class MyClass { }

function annotation(target) {
   target.annotated = true;
}
Nach dem Login kopieren
Nach dem Login kopieren

Nach der Kompilierung:

var _class;

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

function annotation(target) {
  target.annotated = true;
}
Nach dem Login kopieren

Wir können die Dekoration von Klassen sehen, das Prinzip ist:

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;
Nach dem Login kopieren

Zusammenstellung der Dekorationsmethoden

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 dem Login kopieren

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;
}
Nach dem Login kopieren

Kompilierte Quellcode-Analyse der Dekorationsmethode

Wir können sehen, dass Babel eine _applyDecoratedDescriptor-Funktion für erstellt hat Dekorieren Sie die Methode.

Object.getOwnPropertyDescriptor()

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,
// }
Nach dem Login kopieren

Der erste Teil der Quellcode-Analyse

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;
}
Nach dem Login kopieren

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);
Nach dem Login kopieren

Babel Kompiliert als:

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

Zu diesem Zeitpunkt verfügt der an die Funktion _applyDecoratedDescriptor übergebene Deskriptor über das Initialisierungsattribut.

Der zweite Teil der Quellcode-Analyse

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);
Nach dem Login kopieren

Für eine Methode werden mehrere Dekoratoren angewendet, wie zum Beispiel:

class MyClass {
  @unenumerable
  @readonly
  method() { }
}
Nach dem Login kopieren

Babel wird kompiliert zu:

_applyDecoratedDescriptor(
    _class.prototype,
    "method",
    [unenumerable, readonly],
    Object.getOwnPropertyDescriptor(_class.prototype, "method"),
    _class.prototype
)
Nach dem Login kopieren

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.

Teil 3 Quellcode-Analyse

/**
 * 第三部分
 * 设置要 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;
Nach dem Login kopieren

Wenn desc ein Initialisierungsattribut hat, bedeutet dies, dass beim Dekorieren des Klassenattributs der Wert von value auf Folgendes gesetzt wird:

desc.initializer.call(context)
Nach dem Login kopieren

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;
  }
}
Nach dem Login kopieren

ist. Letztendlich wird es so sein ausgeführt werden:

Object["define" + "Property"](target, property, desc);
Nach dem Login kopieren

Es ist ersichtlich, dass die Dekorationsmethode im Wesentlichen mit Object.defineProperty() implementiert wird.

Anwendung

1.log

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);
Nach dem Login kopieren

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;
    }
  }
};
Nach dem Login kopieren

2.autobind

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

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

getPerson() === person;
// true
Nach dem Login kopieren

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>
    );
  }
}
Nach dem Login kopieren

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)
  };
}
Nach dem Login kopieren

3 .debounce

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>
    );
  }
}
Nach dem Login kopieren

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()
      }
    };
  }
}
Nach dem Login kopieren

4.time

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);
        }
      }
    }
  }
}
Nach dem Login kopieren

5.mixin

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"
Nach dem Login kopieren

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]);
        }
      }
    }
  };
}
Nach dem Login kopieren

6.redux

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);
Nach dem Login kopieren

Mit dem Decorator können Sie den obigen Code umschreiben.

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

Relativ gesehen scheint die letztere Schreibweise leichter zu verstehen.

7. Hinweis

Die oben genannten Methoden werden alle zum Ändern der Klassenmethoden verwendet:

const method = descriptor.value;
Nach dem Login kopieren

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();
Nach dem Login kopieren

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!

Verwandte Etiketten:
Quelle:segmentfault.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage