Javascript装飾器の原理

11834 ワード

@で始まる記述的な言葉.英語のdecorator動詞はdecorateです.飾りという意味です.その中の語根dek(dec発音)は元のインドヨーロッパ語では「受け入れる」という意味です.つまり、元のあるものは新しいものを受け入れます.もう一つの観点から説明すると、装飾器は主にその内部に侵入するのではなく、装飾対象の外部で働く.装飾器モードはMVC、IoCなどに弱いが、優れたモデルといえる開発モードでもある.
JavaScriptの装飾器はPythonから参考にしたのかもしれません.Javaかもしれません.比較的に明らかな違いは、ほとんどの言語の装飾器は一行に分かれていなければなりません.JSの装飾器は一行にあります.
装飾器の存在意義
怠けることができるプログラマーこそ、優れたプログラマーです.
例えば、社員カードを持って本社ビルに入ります.従業員の所属部署や等級が違っていますので、ビルのどの部屋にも入れません.どの部屋にもドアがあります.じゃ、会社は各事務室に少なくとも一人の人を配置して、来訪者の検証に関する仕事をします.
  • 訪問者
  • を登録します.
  • アクセス権限があるかどうかを検証し、ない場合は
  • から離れるように要求する.
  • は、その出発時間
  • を記録する.
    もう一つの選択方法は、電子錠を設置し、ドアロックは社員カードの情報を機械室に伝達するだけで、特定の手順で検証することです.
    前者はとりあえずバカモードといいますが、コードは以下の通りです.
    function A101(who){
      record(who,new Date(),'enter');
      if (!permission(who)) {
        record(who,new Date(),'no permission')
        return void;
      }
      //     
      doSomeWork();
      record(who,new Date(),'leave')
    }
    
    function A102(who){
    record(who,new Date(),'enter');
      if (!permission(who)) {
        record(who,new Date(),'no permission')
        return void;
      }
      //     
      doSomeWork();
      record(who,new Date(),'leave')
    }
    
    // ... 
    経験のある人はきっと第一時間に考えています.これらの重複語句を一つの方法にパッケージして、統一的に呼び出します.はい、このようにすれば大部分の問題を解決できますが、まだ「優雅さ」が足りません.また、もう一つの問題があります.もし「部屋」が特に多くて、あるいはビルの奇数号室だけが偶数を検証して検証しないと、「変態」ではないですか?装飾器モードを使用すれば、コードは以下のようになります.
    @verify(who)
    class Building {
      @verify(who)
      A101(){/*...*/}
      @verify(who)
      A102(){/*...*/}
      //...
    }
    verifyは検証された装飾器であり、その本質は関数のセットである.
    JavaScript装飾器
    先の例のように、装飾器自体は、装飾されたオブジェクトを実行する前に実行される関数です.
    JavaScriptには、装飾器の種類があります.
  • アクセス方法(属性のgetとset)
  • フィールド
  • メソッド
  • パラメータ
  • 現在の装飾器の概念はまだ提案段階にあり、正式に利用できるJS機能ではないので、この機能を使用するには、翻訳機のツール、例えばBabelツールやType ScriptによってJSコードをコンパイルしてから実行されます.まず運行環境を構築し、いくつかのパラメータを配置する必要があります.(以下の過程で、NodeJS開発環境及びパッケージ管理ツールが正しくインストールされていると仮定する)
    cd project && npm init
    npm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator
    .babelrcのプロファイルを作成します.
    {
      "presets": ["@babel/preset-env"],
      "plugins": [
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        ["@babel/plugin-proposal-class-properties", { "loose": true }],
        "babel-plugin-parameter-decorator"
      ]
    }
    次の変換命令を利用して、ES 5の変換プログラムを得ることができます.
    npx babel source.js--out-file targt.js
    クラス装飾器
    装飾器を使用したJSプログラムdecorate-class.jsを作成します.
    @classDecorator
    class Building {
      constructor() {
        this.name = "company";
      }
    }
    
    const building = new Building();
    
    function classDecorator(target) {
      console.log("target", target);
    }
    以上は一番簡単な飾り付けプログラムです.Babelを利用してES 5のプログラムを翻訳して、美化してから次の手順を得ます.
    "use strict";
    
    var _class;
    
    function _classCallCheck(instance, Constructor) {
      if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    }
    
    var Building =
      classDecorator(
        (_class = function Building() {
          _classCallCheck(this, Building);
    
          this.name = "company";
        })
      ) || _class;
    
    var building = new Building();
    
    function classDecorator(target) {
      console.log("target", target);
    }
    12行目はクラス生成中に関数形態の装飾器を呼び出して、その中にコンストラクタ(クラス自体)を送り込むことです.装飾器の最初のパラメータはクラスの構造関数の由来であることも明らかにした.
    メソッド装飾器
    コードを少し修正しても、できるだけ簡単なままにします.
    class Building {
      constructor() {
        this.name = "company";
      }
      @methodDecorator
      openDoor() {
        console.log("The door being open");
      }
    }
    
    const building = new Building();
    
    function methodDecorator(target, property, descriptor) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (descriptor) {
        console.log("descriptor", descriptor);
      }
      console.log("=====end of decorator=========");
    }
    そしてコードを変換すると、今回のコード量は急に多く増加していることが分かります._classCallCheck_defineProperties_createClass_applyDecoratedDescriptorの3つの関数を除外し、targetの関数に注目する.
    function _applyDecoratedDescriptor(
      target,
      property,
      decorators,
      descriptor,
      context
    ) {
      var desc = {};
      Object.keys(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;
      }
      desc = decorators
        .slice()
        .reverse()
        .reduce(function (desc, decorator) {
          return decorator(target, property, desc) || desc;
        }, desc);
      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.defineProperty(target, property, desc);
        desc = null;
      }
      return desc;
    }
    この関数はコンストラクタを生成した後に実行され、特にこの装飾関数は配列形式のパラメータで伝達されることに注意されている.その後、上述のコードの17~22行に行って、装飾器を一つずつ適用します.ここで装飾器に対する呼び出しは21行目です.これは3つのパラメータを送信し、propertyはクラス自体を指す.descは、四角法名(または属性名)を指し、_applyDecoratedDescriptorは、前の装飾器によって処理されたかもしれないdescriptorであり、第1のループまたは1つの装飾器だけであれば、方法または属性自体のdescriptorである.
    アクセサ(accessor)装飾
    JSクラスの定義では、getとsetキーワードがあるフィールドを設定する読み書き操作ロジックに対応しています.装飾器もこのような方法の操作をサポートしています.
    class Building {
      constructor() {
        this.name = "company";
      }
      @propertyDecorator
      get roomNumber() {
        return this._roomNumber;
      }
    
      _roomNumber = "";
      openDoor() {
        console.log("The door being open");
      }
    }
    気になる読者はすでに発見したかもしれません.アクセサの装飾コードは上の方法と非常に近いです.属性getとset方法については、それ自体も方法の特殊な形態である.だから彼らのコードは非常に近いです.
    プロパティ装飾器
    ソースコードを変更し続ける:
    class Building {
      constructor() {
        this.name = "company";
      }
      @propertyDecorator
      roomNumber = "";
    }
    
    const building = new Building();
    
    function propertyDecorator(target, property, descriptor) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (descriptor) {
        console.log("descriptor", descriptor);
      }
      console.log("=====end of decorator=========");
    }
    
    変換後のコードは、または上記の属性、アクセサのコードに非常に近いです.しかし、_initializerDefinePropertyに加えて、verify(who)関数が追加されました.この関数は、コンストラクタを生成する際に、宣言されたさまざまなフィールドをオブジェクトに結合します.
    パラメータ装飾器
    パラメータ装飾器の使用位置は前のセットとは少し違っています.行の中で使われています.
    class Building {
      constructor() {
        this.name = "company";
      }
      openDoor(@parameterDecorator num, @parameterDecorator zoz) {
        console.log(`${num} door being open`);
      }
    }
    
    const building = new Building();
    
    function parameterDecorator(target, property, key) {
      console.log("target", target);
      if (property) {
        console.log("property", property);
      }
      if (key) {
        console.log("key", key);
      }
      console.log("=====end of decorator=========");
    }
    変換後のコードの違いは明らかです.Babelは特定の関数を生成して特有の操作をしていません.クラス(コンストラクタ)の作成と関連する属性、方法の作成後に直接開発者自身が作成した装飾器関数を呼び出しました.
    var Building = /*#__PURE__*/function () {
      function Building() {
        _classCallCheck(this, Building);
    
        this.name = "company";
      }
    
      _createClass(Building, [{
        key: "openDoor",
        value: function openDoor(num, zoz) {
          console.log("".concat(num, " door being open"));
        }
      }]);
    
      parameterDecorator(Building.prototype, "openDoor", 1);
      parameterDecorator(Building.prototype, "openDoor", 0);
      return Building;
    }();
    装飾器アプリケーション
    使用パラメータ——クローズド
    以上のすべてのケースにおいて、飾り器自体はパラメータを使用していません.しかし、実用的なアプリケーションでは、しばしば特定のパラメータが必要です.私たちはもう一つの最初の例に戻ります.ここでは、一つのアイデンティティ変数が入る必要があります.どこでどうしますか?装飾器のコードを少し変えます.
    const who = "Django";
    @classDecorator(who)
    class Building {
      constructor() {
        this.name = "company";
      }
    }
    変換して得ます
    // ...
    var who = "Django";
    var Building =
      ((_dec = classDecorator(who)),
      _dec(
        (_class = function Building() {
          _classCallCheck(this, Building);
    
          this.name = "company";
        })
      ) || _class);
    // ...
    4番目の5行目に注意してください.まず装飾器を実行してから戻り値でクラス(構造関数)を送ります.それに対して、構造関数を次のように書くべきです.
    function classDecorator(people) {
      console.log(`hi~ ${people}`);
      return function (target) {
        console.log("target", target);
      };
    }
    同じように、方法、アクセサ、属性、パラメータ装飾器も同じです.
    デコレーションの包み方
    ここで、装飾器のパラメータを対象として、論理的な操作ができます.文章の冒頭の例に戻ります.まず来訪者の権限を検証して記録し、最後に訪問者が離れる時にもう一度記録してください.この時は、監視対象方法が呼び出されたプロセス全体が必要です.
    その方法の装飾器のdescriptorに注目してください.私たちはこのオブジェクトを使って「書き換え」することができます.
    class Building {
      constructor() {
        this.name = "company";
      }
    
      @methodDecorator("Gate")
      openDoor(firstName, lastName) {
        return `The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.`;
      }
    }
    
    let building = new Building();
    console.log(building.openDoor("django", "xiang"));
    
    function methodDecorator(door) {
      return function (target, property, descriptor) {
        let fn = descriptor.value;
        descriptor.value = function (...args) {
          let [firstName, lastName] = args;
          console.log(`log: ${firstName}, who are comming.`);
          // verify(firstName,lastName)
          let result = Reflect.apply(fn, this, [firstName, lastName]);
          console.log(`log: ${result}`);
          console.log(`log: ${firstName}, who are leaving.`);
          return result;
        };
        return descriptor;
      };
    }
    コード第17行、元の方法を一時保存する.18行は新しい方法を定義し、20~25行は記録、検証、記録が離れる動作を記録します.
    log: Django, who are comming.
    log: The door will be open, when Django Xiang is walking in to the company.
    log: Django, who are leaving.
    The door will be open, when Django Xiang is walking in to the company
    装飾の順番
    変換されたコードを読むことによって、装飾器の動作の時刻はクラスが実用化される前に、生成中に装飾関数の動作が完了することが分かります.では、異なるタイプの複数の装飾器が同時に作用する場合、そのプロセスはどうなりますか?以前のケースを全部まとめてみます.
    const who = "Django";
    @classDecorator(who)
    class Building {
      constructor() {
        this.name = "company";
      }
    
      @propertyDecorator
      roomNumber = "";
    
      @methodDecorator
      openDoor(@parameterDecorator num) {
        console.log(`${num} door being open`);
      }
    
      @accessorDecorator
      get roomNumber() {
        return this._roomNumber;
      }
    }
    
    const building = new Building();
    
    function classDecorator(people) {
      console.log(`class decorator`);
      return function (target) {
        console.log("target", target);
      };
    }
    
    function methodDecorator(target, property, descriptor) {
      console.log("method decorator");
    }
    
    function accessorDecorator(target, property, descriptor) {
      console.log("accessor decorator");
    }
    
    function propertyDecorator(target, property, descriptor) {
      console.log("property decoator");
    }
    
    function parameterDecorator(target, property, key) {
      console.log("parameter decorator");
    }
  • class decorator
  • parameter decorator
  • property decoat
  • method decorator
  • accessor decorator
  • 変換後のソースコードを読むことによって実行順序を得ることもできます.
  • 類の装飾器(最外階)
  • パラメータ装飾器(コンストラクタを生成する最奥層)
  • は、属性、方法、およびアクセサ
  • の順序で出現した.
    締め括りをつける は優雅な開発モードであり、開発者符号化プロセスを大いに便利にしながら、コードの読み取り可能性を向上させています.装飾器を使って開発するには、その運行メカニズムを知る必要があります.
    本文はブログから同文多発などの運営ツールプラットフォームを発表します.
    OpenWriteリリース