やっぱり JavaScript (TypeScript) でも アノテーション (Decorator) 使いたい!

JavaScript でもアノテーションしたい!

こんにちは、江嵜です。

JavaScript バリバリ書いてますか?

JavaScript をガッツリ書いていると、

  • やっぱり型が欲しくなったり
  • Enum 欲しくなったり
  • Interface 欲しくなったり

みたいなことあって、TypeScript 使っちゃうんですね。
やっぱりあった方が便利ですからね。

で、次はアノテーション欲しいんですか?
TypeScript にもありますよ!良かったですね!

(もう Java 書いてればいいんじゃないかって気がしなくもない)

実際に書いてみよう!

先ほどはアノテーションという名前を使いましたが、これは Java での呼び方ですね。
JavaScript ではデコレーターという名前になっています。

大体出来ることは同じなので、呼び方が違うんだなという程度の考え方で OK です。

デコレーターは、あるクラスやメソッド、パラメーターに対して、その動きを追加したり変更したりと、
デコレーション出来る機能なんですね!

今回は、一番分かりやすいメソッドに対するデコレーターを見ていきましょう。

まずはデコレーターなしで同じことをやってみる

…と、まずはデコレーターなしで同じことを書いてみましょう。

今回は TypeScript で書いていきますね。

ちなみに、デコレーターはまだ実験段階の内容の為、
TypeScript でのコンパイル時にオプションを指定してあげる必要があります。

とりあえず

npm install -g typescript

とでもして、 TypeScript をインストールしておきましょう。

サンプルはこんな感じ。

test1.ts

class Test {
  main() {
    console.log(’exec main!’);
  };
}

const test = new Test();
test.main();

Test クラスからオブジェクトを作成して、 main メソッドを呼び出すだけですね。

コンパイルして実行してみます

tsc --target ES5 test1.ts
node test1.js
exec main!

ここまではいいですよね。

さて、ここで(定番ですが)、main 実行前にログを出力するするようにしたいとしましょう。

function log(fn: Function) {
  console.log(’exec log!’);
  return fn();
};

class Test {
  main() {
    console.log(’exec main!’);
  }
}

const test = new Test();
log(test.main);

こんな感じですかね。

JavaScript では引数に関数を渡せますから、
log 関数の引数に main の関数を渡して、
ログを出力後に渡された関数を実行する、という流れでいきました。

ここでやったのは、log という関数を別で定義して、main 関数を渡すことで、
main の動作時に log の動作を付け加えた、ということになります。

デコレーターを使ってみる

さて、ではここでデコレーターを使ってみましょう。

先ほどと同じような内容で、デコレーターを使ってみるとこんな感じ。

function log() {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

    // デコレーターが設定された関数を origin に保存
    const origin = descriptor.value;

    // 関数を書き換える
    descriptor.value = function(...args) {
      console.log(’decorate exec!’);

      // 元の関数を呼ぶ
      origin.apply(this, args);
    };
  }
};

class Test {
  @log()
  main() {
    console.log(’exec main!’);
  }
}

const test = new Test();
test.main();

なんか突然難しそうな感じになりましたね?
実際の内容はそうでもないので、一つずつみていきましょう。

function log() {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

    // デコレーターが設定された関数を origin に保存
    const origin = descriptor.value;

    // 関数を書き換える
    descriptor.value = function(...args) {
      console.log(’decorate exec!’);

      // 元の関数を呼ぶ
      origin.apply(this, args);
    };
  }
};

この部分がデコレーターの設定ですね。
デコレーターもその実、単純に関数なので、 function で定義します。

return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

次に、 function を返り値にしていますが、
これは実際にデコレーターが関数にくっつけて使用されたときに実行される関数になります。
(デコレーターを付けた関数が呼び出されたときではなく、使用された時点ですね。)

この時、引数には

  • target: クラスのプロトタイプ
  • name: 呼ばれたメソッドの名前 (今回は test.main なので、 main になります)
  • descriptor: 呼ばれたメソッドの定義

ここで、 descriptor が少し難しいところなのですが、 Object.defineProperty()
3 つめの引数に設定するものと同じですね。
とりあえず、 descriptor.value に元々のメソッド、
今回の場合は console.log(’exec main!’); をする関数が入っていることを覚えておいてください。

ここで、呼ばれたメソッドの定義が渡ってきているので、
これを編集すると、定義を書き換えることが出来る、ということになります。

ということで、

const origin = descriptor.value;

ここで一度、元々の関数の定義を退避しまして、

descriptor.value = function(...args) {

で、新しい関数の定義を代入しましょう。

引数の

...args

は、引数を配列ですべて受け取る書き方ですよ。
(例えば function hoge(…args) {} に対して、 hoge(1, ‘fuga’, true) と呼び出すと、
args は [1, ‘fuga’, true] の配列になります)

今回は元の関数の実行前にログを出力したかったので、
まず

console.log(’decorate exec!’);

でログの出力。

それに続けて、

origin.apply(this, args);

で、先ほど退避させておいた関数を、引数を渡して実行しています。
(apply は第一引数に実行時の this を指定、第二引数に実行時の引数を配列の形で渡すことで、
関数を実行してくれる命令ですね)

ここまでできればデコレーターの設定は終わりです。

最後に、

  @log()
  main() {

のように、デコレーターを適用したいメソッドに対して、
@関数名() として頭にくっつけてあげれば、デコレーターを使用してメソッドを呼び出せるという寸法ですね。

まとめ

まずは、というところで、デコレーターの書き方を学んでみました。
いかがでしょうか。
もしかしたら、まだ何がどう便利なのか、というところがいまいちピンと来ていないかもしれないですね。

ここで注目したいのは、デコレーターを使えば
middleware のように前後に処理を追加することもできますが、
関数の挙動そのものも変えることが出来てしまう、ということです。
(今回は apply で元の関数をそのまま呼び出しましたが、そこを工夫すると色々できそうですよね)

例えば、 Express を使う際に普通であればルーターを使用して

app.get(’/hoge’, function(req, res) {
  //処理
});

みたいに書いて、 /hoge に Get でアクセスされたときの挙動を書いたりしますが、
デコレーターを使えば

@Get(’/hoge’)

みたいなデコレーターを使って定義が書けるようになったりしそうですよね?

皆さんも、JavaScript でデコレーターというものを使う選択肢で、
ラクをしてみてくださいね。
(2019/03 現在では、まだ実験段階の為、仕様が変わる可能性にはお気をつけて!)

Takato Ezaki

Takato Ezaki小中高の塾講師からエンジニア

投稿者プロフィール

福岡で Web 系のエンジニアをしています。

中高の理科教師免許を取り、起業に 2 年間トライした後エンジニアの道へ入りました。

化学反応の中では Belousov-Zhabotinsky 反応が大好きです。

この著者の最新の記事

関連記事

コメントは利用できません。

募集中!(o゜▽゜)o

エンジャパン
求む、社長!
follow us in feedly

コッチもヨロシク!





最近のネタ!

  1. 2019-9-17

    2019年8月度社員総会&懇親会@Nagoya

    気づけば夏の暑さも去ろうとしています。そんな9月うまれのおみです。 さて!少しお時間空いてしまいま…
  2. 2019-9-16

    [JavaScript] オブジェクト & 配列のイマドキなコピー方法 (スプレッド構文)

    オブジェクトや配列のコピーってどうやって書きますか? 皆さん JavaScript 書いてますか?…
  3. 2019-9-9

    Web で物理アニメーション・その5(最終回) 「ライブラリを使おう」

    前回の回答 前回の宿題は、バウンド時の跳ね返り方を制御するんでしたね。 では回答。 [code…
  4. 2019-9-6

    第3回 倶楽部ぽじてぃぶ ~釣り~

    2019年7月13日に、第3回となる倶楽部ぽじてぃぶの活動が開催されました! 今回は、その様子をお…
  5. 2019-9-2

    2019年8月度社員総会&懇親会@Osaka

    2019年8月6日(火)、大阪社員総会&懇親会が開催されました! その様子をお届けします! 社員総…
ページ上部へ戻る

当サイトに掲載されているコンテンツ(文書、画像等)は、許可なく複製・転用等する事を禁じます。

「フェアネス方式®」(登録6150741)は、日本国内における株式会社エージェントグローの登録商標です。

当サイトでは最低限必要と考えられる場合において、会社名/サービス名/商品名などを記載している場合があります。
これらはあくまでも説明の必要性に応じて用いているものであり、各社の権利等を侵害を目的とするものではございません。
不適切と考えられる場合には、当社お問い合わせフォームよりご連絡ください。

当サイトでは®や™などの表記を省略させていただいております。