デコレータES2016を分解します





私たちの多くは、おそらく最新のECMAScript標準に関するこの誇大広告にうんざりしているでしょう。 ES6、ES7 ECMAScript Harmony ... JSを適切に呼び出す方法については、誰もが自分の意見を持っているようです。 しかし、こうした誇大広告にもかかわらず、JavaScriptで現在起こっていることは、少なくとも過去5年間でJavaScriptに起こった最も注目すべきことです。 言語は生き続け、発展し、コミュニティは常に新しい機能と構文構造を提供します。 確かに注目に値するこれらの新しいデザインの1つがデコレーターです。 このトピックに関する資料の検索を開始すると、ロシア語を話すインターネット上のデコレータについては実質的に何もないことに気付きました。 同時に、Addy Osmaniは2015年7月に、優れた記事「 Exploring ES2016 Decorators on Medium」を発表しました。 この点で、私はあなたの注意をこの記事のロシア語への翻訳を持ってきて、ここに投稿したいと思います。



イテレータージェネレーターリストのインクルード ...イノベーションごとに、JavaScriptとPythonの違いは小さくなっています。 今日は、ECMAScript 2016標準(別名ES7)のもう1つの「pythonのような」提案-Yehuda Katzのデコレータについてお話します。



デコレータパターン



ES2016のデコレータを使用するためのスコープとルールを分析する前に、他の言語にも同様のものがあるかどうかを確認しましょう。 たとえば、Python。 彼にとって、デコレーターは、 高階関数を簡単に呼び出すことができる単なる構文上の砂糖です 。 つまり、 別の関数を取得し、その動作を変更するだけで、ソースコードは変更しません 。 以下に示すように、想像できる最も単純なデコレータ:



@mydecorator def myfunc(): pass
      
      





例の一番上にある式( "@mydecorator")はデコレーターであり、その構文はES2016(ES7)標準とまったく同じに見えるため、必要な資料の一部を既にマスターしています。



@記号は、パーサーにデコレータを使用していることを示しますが、 mydecoratorは単なる関数の名前です。 この例のデコレーターは、1つのパラメーター(つまり、装飾された関数myfunc )を受け取り 、機能が変更されたまったく同じ関数を返します。



ソースコードを変更せずに関数に追加の動作を追加する場合、デコレータは非常に便利です。 たとえば、メモ化、アクセスレベル、認証、 インスツルメンテーション 、ロギングのサポートを提供するために、本当に多くのユースケースがあり、このリストは延々と続きます。



ES5およびES2015(別名ES6)のデコレーター



命令型アプローチを使用して(つまり、純粋な関数を使用して)E​​S5にデコレーターを実装することは、非常に簡単な作業です。 しかし、ES2015ではネイティブクラスのサポートが導入されたため、同じ目標を提供するには、単なる関数よりも柔軟なものが必要です。



次のECMAScript標準にデコレータを追加するというYehuda Katzの提案は、コードデザイン中にオブジェクトリテラル、クラス、およびそれらのプロパティの注釈と変更を提供し、命令型アプローチではなく宣言型アプローチを使用します。



いくつかのES2016デコレーターを実際の例で見てみましょう!



ES2016デコレーターの動作



ですから、Pythonから学んだことを思い出して、知識をJavaScriptの分野に移そうとします。 先ほど言ったように、ES7の構文も同じであり、主要なアイデアです。 したがって、ES2016のデコレータは、ターゲット、名前、および記述子を引数として取ることができる式です 。 次に、最初に「@」記号を追加して単純に「適用」し、装飾するコードのセクションの直前に配置します。 現在、デコレータはクラスを定義するか、プロパティを定義するために定義できます。



プロパティを飾る



クラスを見てみましょう:



 class Cat { meow() { return `${this.name} says Meow!`} }
      
      





このメソッドをCatクラスのプロトタイプに追加すると、次のようになります。



  Object.defineProperty(Cat.prototype, 'meow', { value: specifiedFunction, enumerable: false, configurable: true, writable: true });
      
      





したがって、メソッドがクラスのプロトタイプに掛けられ、猫が声を出すことができます。 同時に、このメソッドはオブジェクトのプロパティの列挙には関与せず、変更することも書き込み可能です。 しかし、このメソッドの実装への変更を明示的に禁止したいと想像してください。 たとえば、「@ readonly」デコレータを使用してこれを行うことができます。これは、名前が示すように、この読み取り専用メソッドへのアクセスを許可し、オーバーライドを防ぎます。



  function readonly(target, key, descriptor) { descriptor.writable = false; return descriptor; }
      
      





次に、以下に示すように、作成したデコレータをメソッドに追加します。



  class Cat { @readonly meow() { return `${this.name} says Meow!`} }
      
      





デコレータはパラメータを受け入れることもできます。最終的にはデコレータは単なる式なので、「@ readonly」と「@something(パラメータ)」の両方が機能するはずです。



これで、Catクラスのプロトタイプにメソッドをハングさせる前に、エンジンはデコレーターを起動します。



  let descriptor = { value: specifiedFunction, enumerable: false, configurable: true, writable: true }; //     ,   "Object.defineProperty", //       ,  // "Object.defineProperty"   descriptor = readonly(Cat.prototype, 'meow', descriptor) || descriptor; Object.defineProperty(Cat.prototype, 'meow', descriptor);
      
      





したがって、装飾されたmeowメソッドは読み取り専用になります。 この動作を確認できます。



  let garfield = new Cat(); garfield.meow = () => { console.log('I want lasagne!'); }; // !        .
      
      





面白いですね。 すぐにデコレーションクラスを見ていきますが、その前にデコレータのコミュニティサポートについて説明しましょう。 未熟にもかかわらず、デコレータのライブラリ全体が表示され始めます(たとえば、Jay Phelpsのhttps://github.com/jayphelps/core-decorators.js )。 デコレータを作成したように、まったく同じものを使用できますが、ライブラリに実装されます。



  import { readonly } from 'core-decorators'; class Meal { @readonly entree = 'steak'; } let meal = new Meal(); meal.entree = 'salmon'; // !
      
      





ライブラリは、APIを変更するときに非常に便利な「@deprecate」デコレータを実装するため、優れていますが、下位互換性のために古いメソッドを保持する必要があります。 このデコレータに関するドキュメントの内容は次のとおりです。

デコレータはconsole.warn()を呼び出し、警告メッセージを表示します。 このメッセージは再定義できます。 将来の参照用にリンクの追加もサポートしています。




  import { deprecate } from 'core-decorators'; class Person { @deprecate facepalm() {} @deprecate('We are stopped facepalming.') facepalmHard() {} @deprecate('We are stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) facepalmHarder() {} } let captainPicard = new Person(); captainPicard.facepalm(); // DEPRECATION Person#facepalm will be removed in future versions captainPicard.facepalmHard(); // DEPRECATION Person#facepalmHard: We are stopped facepalming. captainPicard.facepalmHarder(); // DEPRECATION Person#facepalmHarder: We are stopped facepalming. // // See http://knowyourmemes.com/memes/facepalm for more details //
      
      





クラスを飾る



次に、装飾クラスを見てみましょう。 提案された仕様に従って、デコレータはターゲットコンストラクタを受け入れます。 たとえば、MySuperHeroクラスのスーパーヒーローデコレータを作成できます。



 function superhero(target) { target.isSuperhero = true; target.power = 'flight'; } @superhero class MySuperHero {} console.log(MySuperHero.isSuperhero); // true
      
      





さらに進んで、デコレーターがパラメーターを受け入れ、ファクトリーメソッドに変換できるようにすることができます。



 function superhero(isSuperhero) { return function (target) { target.isSuperhero = isSuperHero; target.power = 'flight'; } } @superhero(false) class MySuperHero {} console.log(MySuperHero.isSuperhero); // false
      
      





そのため、ES2016のデコレーターはクラスとプロパティ記述子を使用できます。 そして、すでにわかったように、プロパティ記述子とターゲットオブジェクトは自動的に転送されます。 プロパティ記述子にアクセスできるデコレータは、プロパティにゲッターを追加したり、デコレータに入れずに面倒に見える動作を追加したりすることができます。たとえば、プロパティへの最初のアクセス時に現在のエンティティへの関数呼び出しのコンテキストを自動的に変更します。



ES2016デコレータとミキサー



Reg Braithwaite ES2016 Decoratorsの記事をミックスインおよびその以前のFunctional Mixinsとして注意深く読み、デコレーターを使用するための非常に興味深いオプションを思いつきました 。 彼の提案は、動作をプロトタイプまたは特定のクラスのオブジェクトと混合する何らかのヘルパーを導入することです。 この場合、オブジェクトの動作とクラスのプロトタイプを混合する機能ミックスインは次のようになります。



  function mixin(behaviour, sharedBehaviour = {}) { const instanceKeys = Reflect.ownKeys(behaviour); const sharedKeys = Reflect.ownKeys(sharedBehaviour); const typeTag = Symbol('isa'); function _mixin(clazz) { for (let property of instanceKeys) { Object.defineProperty(clazz.prototype, property, { value: behaviour[property] }); } Object.defineProperty(clazz.prototype, typeTag, { value: true}); return clazz; } for(let property of sharedKeys) { Object.defineProperty(_mixin, property, { value: sharedBehaviour[property], enumerable: sharedBehaviour.propertyIsEnumerable(property) }); } Object.defineProperty(_mixin, Symbol.hasInstance, { value: (i) => !!i[typeTag] }); return _mixin; }
      
      





素晴らしい。 これで、いくつかのミックスインを定義し、それらをクラスで装飾することができます。 「ComicBookCharacter」クラスがあると想像してみましょう。



  class ComicBookCharacter { constructor(first, last) { this.firstName = first; this.lastName = last; } realName() { return this.firstName + ' ' + this.lastName; } }
      
      





それは漫画の中で最も退屈なヒーローかもしれませんが、ヒーローに「超大国」とヒーローに「多目的ベットマンのベルト」を追加するいくつかのミックスインを発表することで状況を救うことができます。 これを行うには、上記で発表したミックスインファクトリを使用しましょう。



  const SuperPowers = mixin({ addPower(name) { this.powers().push(name); return this; }, powers() { return this._powers_pocessed || (this._powers_pocessed = []); } }); const UtilityBelt = mixin({ addToBelt(name) { this.utilities().push(name); return this; }, utilties() { return this._utility_items || (this._utility_items = []); } });
      
      





これで、上記で宣言した「ComicBookCharacter」クラスを修飾するために、ミックスインの名前で@構文を使用して、目的の動作を追加できます。 いくつかの装飾命令を一緒に定義できることに注意してください。



  @SuperPowers @UtilityBelt class ComicBookCharacter { constructor(first, last) { this.firstName = first; this.lastName = last; } realName() { return this.firstName + ' ' + this.lastName; } }
      
      





これで、独自のバットマンを作成できます。



  const batman = new ComicBookCharacter('Bruce', 'Wayne'); console.log(batman.realName()); // Bruce Wayne batman .addToBelt('batarang') .addToBelt('cape'); console.log(batman.utilities()); // ['batarang', 'cape'] batman .addPower('detective') .addPower('voice sounds like Gollum has asthma'); console.log(batman.powers()); // ['detective', 'voice sounds like Gollum has asthma']
      
      





ご覧のとおり、この種のデコレータは比較的コンパクトであり、関数の呼び出しの代替として、または高次コンポーネントのヘルパーとして使用できます。



Babelにデコレータを理解させる



デコレーター(執筆時点)はまだ承認されておらず、標準への追加のみが提案されています。 しかし、バベルは実験デザインのトランスピレーションをサポートしているという事実により、デコレーターと友達になれます。



Babel CLIを使用する場合、次のようなデコレーターを有効にできます。

  babel --optional es7.decorators
      
      





または、トランスフォーマーを使用してデコレーターサポートを有効にすることができます。



  babel.transform('code', { optional: ['es7.decorators'] });
      
      





最後に、Babel REPLでそれらをいじることができます(このために、実験デザインのサポートを有効にします)。



プロジェクトでデコレータを使用しないのはなぜですか?



短期的には、ES2016のデコレーターは、宣言的にデコレーション、注釈付け、型チェック、ES2015のクラスと動作を混合するのに非常に役立ちます。 長い目で見れば、それらは静的解析のための非常に有用なツールとして機能します(コンパイル時または自動補完時に型をチェックするツールの作成の推進力として機能します)。



オブジェクトは、同じクラスの他のオブジェクトに影響を与えることなく、静的または動的にパターンを使用して特定の動作で装飾できる従来のOOPのデコレーターと違いはありません。 それにもかかわらず、私はそれらを楽しい追加だと思います。 クラスのデコレータのセマンティクスはまだ少し気味が悪いように見えますが、Yehudaリポジトリをときどき見て、調整しておく必要があります。



便利なリンク



https://github.com/wycats/javascript-decorators

https://github.com/jayphelps/core-decorators.js-------->https://github.com/jayphelps/core-decorators.js

http://blog.developsuperpowers.com/eli5-ecmascript-7-decorators/

http://elmasse.github.io/js/decorators-bindings-es7.html

http://raganwald.com/2015/06/26/decorators-in-es7.html-------->http://raganwald.com/2015/06/26/decorators-in-es7.html



All Articles