JS デコレータ パターンと TypeScript デコレータ

JS デコレータ パターンと TypeScript デコレータ

デコレータパターンの紹介

デコレータ パターン (Decorator Pattern) は、オブジェクト自体を変更せずに追加の責任を動的に追加します。それは一種の構造パターンです。

デコレータ パターンを使用する利点: オブジェクトの主要な責任を、装飾する関数から分離します。非侵襲的な行動修正。

たとえば、平均的な容姿の女の子でも、ビューティー機能の助けを借りて見事な容姿を手に入れることができます。補助的な装飾機能を使いこなし、顔をスリムにしたり、目を大きくしたり、肌を滑らかにしたりして写真を撮れば、驚くほど素敵に見えるでしょう。

この一連の装飾を重ねた後でも、あなたはあなたであり、外見は同じままですが、カメラの前ではより美しく見えるようになります。お好みに応じて、さまざまなデコレーションスタイルを試すこともできます。デコレーション機能がうまくいけば、あなたも「ミスターバラエティスター」になれます。

各関数をクラスに抽象化してコードで表現できます。

// 女の子クラス Girl {
  フェイスバリュー() {
    console.log('私のオリジナルの顔')
  }
}

クラス ThinFace {
  コンストラクタ(女の子) {
    this.girl = 女の子;
  }
  フェイスバリュー() {
    この女の子の顔の値();
    console.log('顔痩せをオンにする')
  }
}

クラスIncreasingEyes{
  コンストラクタ(女の子) {
    this.girl = 女の子;
  }
  フェイスバリュー() {
    この女の子の顔の値();
    console.log('目を大きくしましょう')
  }
}

girl = new Girl();
女の子 = 新しい ThinFace(女の子);
女の子 = 新しい IncreasingEyes(女の子);

// 目をつぶる girl.faceValue(); //

コードのパフォーマンスから判断すると、1 つのオブジェクトを別のオブジェクトに埋め込むことは、1 つのオブジェクトを別のオブジェクトでラップしてラッピング チェーンを形成することと同じです。呼び出し後、パッケージ チェーンに沿って各オブジェクトに渡され、各オブジェクトに処理の機会が与えられます。

この方法は、装飾機能の追加と削除の柔軟性に優れています。本当の顔を見せる勇気があるなら、小顔効果のパッケージを削除するだけです。他の機能には影響しません。肌の滑らかさを追加したい場合は、別の機能クラスを追加して装飾を続けます。他の機能には影響せず、同時に実行できます。

クラスを使用して JavaScript に小さな機能を追加するのは少し面倒です。JavaScript の利点は、柔軟性があり、オブジェクトを使用して表現できることです。

女の子を = {
  フェイスバリュー() {
    console.log('私のオリジナルの顔')
  }
}
関数thinFace() {
  console.log('顔痩せをオンにする')
}
関数IncreasingEyes() {
  console.log('目を大きくしましょう')
}

女の子.faceValue = 関数(){
  const originalFaveValue = girl.faceValue; // オリジナル関数 return function() {
    元のお気に入り値.call(女の子);
    thinFace.call(女の子);
  }
}()
女の子.faceValue = 関数(){
  const originalFaveValue = girl.faceValue; // オリジナル関数 return function() {
    元のお気に入り値.call(女の子);
    IncreasingEyes.call(女の子);
  }
}()

女の子.faceValue();

元のコードに変更を加えずに、まず元の関数を保持し、次に書き換えて、保持した関数を書き換えたコード内で呼び出します。

デコレータ パターンの原理を図で説明します。

図からわかるように、レイヤーごとにパッケージングすることで、元のオブジェクトの機能性が向上します。

TypeScript のデコレータ

TypeScript のデコレータは @expression という形式を使用します。expression は評価された後、実行時に呼び出される関数となり、デコレートされた宣言情報がパラメータとして渡されます。

Javascript 仕様のデコレータは現在、要請の第 2 段階にあります。つまり、ネイティブ コードで直接使用することはできず、ブラウザーではまだサポートされていません。

デコレータ構文は、コンパイル段階で babel または TypeScript ツールを使用してブラウザ実行可能コードに変換できます。 (最後にコンパイルされたソースコードの分析があります)

以下では主に TypeScript でのデコレータの使用について説明します。

TypeScript のデコレータは、クラス宣言、メソッド、アクセサー (ゲッター/セッター)、プロパティ、およびパラメータに添付できます。

デコレータのサポートを有効にするには、コマンドラインでファイルをコンパイルします。

tsc --target ES5 --experimentalDecorators test.ts

設定ファイル tsconfig.json

{
    "コンパイラオプション": {
        "ターゲット": "ES5",
        "実験的デコレータ": true
    }
}

デコレータの使用

デコレータは実際には関数です。使用するときは、その前に @ 記号を追加し、デコレートする宣言の前に記述します。複数のデコレータが同時に同じ宣言に作用する場合は、1 行または新しい行に記述できます。

//新しい行に@test1を記述する
@テスト2
宣言

// @test1 @test2 の行を書き込みます...
宣言

face.ts ファイルを定義します。

関数thinFace() {
  console.log('顔痩せをオンにする')
}

薄い顔
クラス ガール {
}

js コードにコンパイルし、実行時に thinFace 関数を直接呼び出します。このデコレータはクラスに対して動作し、クラス デコレータと呼ばれます。

複数の関数を追加する必要がある場合は、複数のデコレータを組み合わせることができます。

関数thinFace() {
  console.log('顔痩せをオンにする')
}
関数IncreasingEyes() {
  console.log('目を大きくしましょう')
}

薄い顔
もっと見る
クラス ガール {
}

実行時に複数のデコレータが組み合わされる場合、呼び出し順序は下から上になり、書き込み順序と正反対になることに注意してください。例に示されている実行結果は次のとおりです。

「目を大きくする」

「フェイススリミングをオンにする」

デコレータ内のクラスに属性を追加し、それを他のデコレータで使用する場合は、最後のデコレータが最初に呼び出されるため、最後のデコレータ内に記述する必要があります。

デコレーターファクトリー

場合によっては、デコレータにいくつかのパラメータを渡す必要があり、そのためにはデコレータ ファクトリ関数の助けが必要になります。デコレータ ファクトリ関数は、実際には呼び出された後に関数を返す高階関数であり、返された関数がデコレータ関数として機能します。

関数thinFace(値:文字列){
  console.log('1-面を薄くするファクトリーメソッド')
  関数()を返す{
    console.log(`4-私は小顔デコレータです。小顔になりたいです ${value}`)
  }
}
関数IncreasingEyes(値:文字列) {
  console.log('2-目を拡大するファクトリーメソッド')
  関数()を返す{
    console.log(`3-私は目を大きくするデコレータなので、${value}が必要です`)
  }
}

@薄い顔('50%')
@IncreasingEyes('ダブル')
クラス ガール {
}

ファクトリ関数は @ 記号の後に呼び出され、デコレータ関数を取得するために上から下へ実行されます。デコレータ関数の実行順序は、引き続き下から上になります。

実行結果は次のとおりです。

ワンフェイススリミングファクトリー方式

2-目を大きくする工場方式

3-私は目を大きくするデコレーターです。大きさを2倍にしたいです。

4-私は小顔デコレーターです。顔を50%スリムにしたいです

要約すると:

  • ファクトリ関数を記述し、上から下まで実行してデコレータ関数を取得します。
  • デコレータ関数の実行順序は下から上になります。

クラスデコレータ

クラス宣言に基づいて動作するデコレータにより、クラスを変更する機会が得られます。デコレータ関数が実行されると、クラス コンストラクターがデコレータ関数に渡されます。

関数クラスデコレータ(値: 文字列){
  関数(コンストラクタ)を返す{
    console.log('コンストラクタを受信します')
  }
}

関数thinFace(コンストラクタ){
  コンストラクター.プロトタイプ.thinFaceFeature = function() {
    console.log('面細化関数')
  }
}

薄い顔
@classDecorator('クラスデコレータ')
クラス Girl {}

g = new Girl();

g.thinFaceFeature(); // '細い顔の特徴'

上記の例では、転送コンストラクターを取得した後、コンストラクター プロトタイプに新しいメソッドを追加したり、他のクラスを継承したりすることもできます。

メソッドデコレータ

クラスに対して動作するメソッドには、静的メソッドとプロトタイプ メソッドの 2 種類があります。静的メソッドに作用する場合、デコレータ関数はクラス コンストラクターを受け取ります。プロトタイプ メソッドに作用する場合、デコレータ関数はプロトタイプ オブジェクトを受け取ります。
これをプロトタイプ方式に適用した例を示します。

関数メソッドDecorator(値: 文字列, Girl){
  関数(プロトタイプ、キー、記述子)を返す{
    console.log('プロトタイプ オブジェクト、装飾されたプロパティ名、プロパティ記述子を受信します'、Girl.prototype === prototype)
  }
}

関数thinFace(プロトタイプ、キー、記述子){
  // 元のメソッドのロジックを保持します。let originalMethod = description.value;
  // 書き直し、ロジックを追加し、元のロジックを実行します。descriptor.value = function(){
    originalMethod.call(this); // これは console.log('顔痩せモードをオンにする') を指していることに注意してください
  }
}

クラス ガール {

  薄い顔
  @methodDecorator('メソッド デコレータ', Girl)
  フェイスバリュー(){
    console.log('私の本来の姿')
  }
}

g = new Girl();

g.faceValue();

コードからわかるように、デコレータ関数はプロトタイプ オブジェクト、メソッド名、説明オブジェクトの 3 つのパラメータを受け取ります。説明されているオブジェクトに詳しくない場合は、こちらを参照してください。

機能を強化するには、元の関数を保持し、説明オブジェクトの値を別の関数に書き換えることができます。

g.faceValue() アクセス メソッドを使用すると、説明オブジェクトの値に対応する値にアクセスします。

書き換えた関数にロジックを追加し、保持された元の関数を実行します。元の関数では、これをプロトタイプ オブジェクトにポイントするために call または apply を使用する必要があることに注意してください。

不動産デコレーター

これは、クラスで定義された属性に対して作用します。これらの属性はプロトタイプ上の属性ではなく、クラスをインスタンス化することによって取得されるインスタンス オブジェクト上の属性です。

デコレータは、プロトタイプ オブジェクトと属性名という 2 つのパラメータも受け入れます。しかし、オブジェクトを記述する属性がありません。なぜでしょうか?これは、TypeScript がプロパティ デコレータを初期化する方法に関係しています。 現在、プロトタイプ オブジェクトのメンバーを定義するときにインスタンス属性を記述する方法はありません。

関数プロパティデコレータ(値: 文字列、Girl){
  関数(プロトタイプ、キー)を返す{
    console.log('プロトタイプ オブジェクト、装飾されたプロパティ名、プロパティ記述子を受信します'、Girl.prototype === prototype)
  }
}

関数thinFace(プロトタイプ、キー){
  console.log(プロトタイプ、キー)
}

クラス ガール {
  薄い顔
  @propertyDecorator('プロパティ デコレータ', 女の子)
  公年齢: 数値 = 18;
}

g = new Girl();

console.log(g.age); // 18

デコレータを書く他の方法

以下は複数のデコレータの組み合わせで、上記の 3 つに加えて、アクセサ デコレータとパラメータ デコレータもあります。これらのデコレータを組み合わせると、実行順序が決まります。

関数クラスデコレータ(値: 文字列){
  console.log(値)
  関数(){}を返す
}
関数プロパティデコレータ(値: 文字列) {
  console.log(値)
  関数()を返す{
    コンソールログ('プロパティデコレータ')
  }
}
関数メソッドデコレータ(値: 文字列) {
  console.log(値)
  関数()を返す{
    console.log('メソッドデコレータ')
  }
}
関数paramDecorator(値: 文字列) {
  console.log(値)
  関数()を返す{
    コンソールログ('paramDecorator')
  }
}
関数AccessDecorator(値:文字列) {
  console.log(値)
  関数()を返す{
    console.log('アクセスデコレータ')
  }
}
関数thinFace(){
  console.log('薄い顔')
}
関数IncreasingEyes() {
  console.log('目を大きくしましょう')
}


薄い顔
@classDecorator('クラスデコレータ')
クラス ガール {
  @propertyDecorator('プロパティデコレータ')
  年齢: 数字 = 18;
  
  @AccessDecorator('アクセス デコレータ')
  都市を取得します(){}

  @methodDecorator('メソッドデコレータ')
  もっと見る
  フェイスバリュー(){
    console.log('元の顔')
  }

  getAge(@paramDecorator('パラメータデコレータ') 名前: 文字列){}
}

このコンパイルされたコードを実行すると、これらのアクセサの順序がプロパティ デコレータ -> アクセサ デコレータ -> メソッド デコレータ -> パラメータ デコレータ -> クラス デコレータであることがわかります。

より詳しい使用方法については、公式ウェブサイトのドキュメントを参照してください: https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories

デコレータのランタイムコード分析

デコレータはブラウザではサポートされていないため、直接使用することはできません。ツールを使用してブラウザで実行可能なコードにコンパイルする必要があります。

ツールによってコンパイルされたコードを分析します。

face.js ファイルを生成します:

tsc --target ES5 --experimentalDecorators face.ts

face.js ファイルを開くと、フォーマットできる圧縮コードが表示されます。

まずこのコードを見てみましょう:

__飾る([
    propertyDecorator('プロパティデコレータ')
], Girl.prototype, "age", void 0);
__飾る([
    AccessDecorator('アクセス デコレータ')
], Girl.prototype, "city", null);
__飾る([
    methodDecorator('メソッドデコレータ'),
    増加する目
], Girl.prototype, "faceValue", null);
__飾る([
    __param(0, paramDecorator('パラメータデコレータ'))
], Girl.prototype, "getAge", null);
女の子 = __decorate([
    薄い顔、
    classDecorator('クラスデコレータ')
]、 女の子);

__decorate の機能は、デコレータ関数を実行することです。このコードからは多くの情報が得られ、上記の結論を裏付けています。

__decorate の呼び出し順序から、複数のタイプのデコレータが一緒に使用される場合の順序は、属性デコレータ -> アクセサ デコレータ -> メソッド デコレータ -> パラメータ デコレータ -> クラス デコレータであることがわかります。

__decorate 関数が呼び出され、渡されるパラメータは使用されるデコレータのタイプによって異なります。

渡される最初のパラメータは同じで、配列であるため、記述した順序と一致します。各項目は評価されたデコレータ関数です。@propertyDecorator() が記述されている場合は、すぐに実行されてデコレータ関数が取得されます。これは、上記の分析と一致しています。

クラス デコレータはクラスを 2 番目の引数として受け取り、他のデコレータはプロトタイプ オブジェクトを 2 番目の引数として受け取り、プロパティ名を 3 番目の引数として受け取り、4 番目の引数は null または void 0 になります。 void 0 の値は未定義であり、パラメータが渡されないことを意味します。

__decorate 関数に渡されるパラメータの数と値を覚えておいてください。__decorate ソースコードを詳しく調べると、これらの値を使用して、デコレータ関数を実行するときに渡すパラメータの数が決まります。

さて、__decorate 関数の実装を見てみましょう。

// この関数は既に存在するので直接使用するか、自分で定義してください。 var __decorate = (this && this.__decorate) ||
// 4 つのパラメータを受け取ります: 
//デコレータはデコレータ関数の配列とターゲットプロトタイプオブジェクト|クラスを格納します。
//キー属性名、desc 説明 (未定義または null)
関数(デコレータ、ターゲット、キー、説明) {
  var c = 引数.長さ、
  // パラメータの数を取得します r = c < 3 // パラメータの数が 3 未満の場合、クラスデコレータであることを意味し、クラスを直接取得しますか? ターゲット
    : desc === null // 4 番目のパラメータが null の場合、オブジェクトを説明する必要があります。プロパティ デコレータは void 0 を渡し、説明オブジェクトはありません。
        ?desc = Object.getOwnPropertyDescriptor(ターゲット、キー) 
        : 説明,
  d;
  // Reflect.decorate メソッドが提供されている場合は直接呼び出します。そうでない場合は自分で実装します if (typeof Reflect === "object" && typeof Reflect.decorate === "function") 
    r = Reflect.decorate(デコレータ、ターゲット、キー、説明);
  それ以外 
    // デコレータ関数の実行順序は、記述された順序とは逆で、下から上へ実行されます。for (var i = decorators.length - 1; i >= 0; i--) 
      if (d = decorators[i]) // デコレータ関数を取得します r = (c < 3 // パラメータの数が 3 未満の場合、クラスデコレータであることを意味します。デコレータ関数を実行し、クラスに直接渡しますか? d(r) 
            : c > 3 // パラメータの数が 3 より大きく、メソッド デコレータ、アクセサ デコレータ、またはパラメータ デコレータの場合は、渡された説明オブジェクトを実行しますか? d(target, key, r) 
              : d(target, key) // これはプロパティ デコレータであり、説明オブジェクトは渡されません) || r;

  // 装飾された属性(主にメソッドと属性)の説明オブジェクトを設定します/*** 
     * r の値には 2 つのケースがあります。
     * 1 つは上記の Object.getOwnPropertyDescriptor を通じて取得された値です。 * もう 1 つはデコレータ関数が実行された後の戻り値であり、説明オブジェクトとして使用されます。
     * 一般に、デコレータ関数は値を返しません。
    */
  c > 3 && r && Object.defineProperty(target, key, r),r を返します。
};

上記のパラメータ デコレータは、__params という関数を呼び出します。

var __param = (this && this.__param) || function (paramIndex, デコレータ) {
    関数 (ターゲット、キー) を返します { デコレータ (ターゲット、キー、paramIndex); }
};

目的は、パラメータ位置 paramIndex をデコレータ関数に渡すことです。

コンパイルされたソース コードを読めば、デコレータについてより深く理解できると思います。

以上がJSデコレータパターンとTypeScriptデコレータの詳細です。JSの詳細については、123WORDPRESS.COMの他の関連記事にも注目してください。

以下もご興味があるかもしれません:
  • PHP、Python、Javascript のデコレータ パターンの比較
  • 1 つの記事で JavaScript デコレータ パターンを理解する

<<:  MySQL クイックデータ比較テクニック

>>:  クラウド サーバー Ubuntu_Server_16.04.1 に MySQL をインストールしてリモート接続を有効にする方法

推薦する

HTMLコードに基づく画像断片化読み込み機能の実装

今日は断片化された画像の読み込み効果を実装します。効果は次のとおりです。 これを 3 つのステップで...

MySQL インデックスの正しい使い方とインデックスの原理の詳細な説明

1. はじめになぜインデックスが必要なのでしょうか?一般的なアプリケーション システムでは、読み取り...

複数の .sql ファイルを MySQL に効率的にインポートする方法の詳細な説明

MySQL には、複数の .sql ファイル (SQL ステートメントを含む) をインポートする方法...

vsftpd ユーザーが ssh 経由でログインすることを禁止する方法

序文vsftp は使いやすく安全な FTP サーバー ソフトウェアです。システムユーザーまたは仮想ユ...

Linux Bash スクリプトを使用してユーザーを識別する方法の例

多くの場合、bash スクリプト内またはスクリプト自体内で直接 sudo を使用してコマンドを実行す...

Dockerコンテナのデータボリュームの詳細な説明

何ですかまず、Docker の概念を見てみましょう。アプリケーションと実行環境をコンテナにパッケージ...

MySQL のデータ統計に関するヒント

よく使用されるデータベースである MySQL では、多くの操作が必要です。デジタル操作には非常に便利...

Rails APIを使用してReactアプリケーションを構築するための詳細な手順

目次バックエンド: Rails API部分フロントエンド: React部分Reactコンポーネントa...

CSS3 疑似クラスセレクターの簡単なレビュー

序文CSS がフロントエンド開発の基本的なスキルであるならば、「セレクター」は基礎中の基礎です。これ...

MySQL よく使われる関数の詳細な概要

目次MySQL 共通関数1. 数値関数文字列関数3. 時間機能4. システム機能5. 集計関数MyS...

CSS3で実装されたグラデーションスライド効果

成果を達成する コードhtml <div class="css-slideshow&...

HTML チュートリアル: よく使われる HTML タグのコレクション (4)

導入された HTML タグは、必ずしも XHTML 仕様に完全に準拠しているわけではありません。実際...

リモートホスト上でスクリプトや命令を実行する Zabbix の詳細な説明

シナリオ要件1. zabbix_server Web インターフェースのスクリプト機能を使用すると、...

MySQL のキーとインデックスの違い

まずはコードを見てみましょう: ALTER TABLE reportblockdetail ADD ...

ウェブサイトに最も必要なのは、ターゲットユーザーグループのエクスペリエンスを向上させることです。

「大河は東に流れ、波は歴代の英雄たちを洗い流した。古城の西側は三国時代の周朗の赤壁だと言われている...