TypeScript 1.5 Alphaに入ったDecoratorsは何をするのか

皆さんTypeScript使ってますか？
先にあげた [ES6 modulesの記事](http://qiita.com/nana4gonta/items/2466f7ca5b3607fbfdb4)に続いてDecoratorsについて書こうと思ってたら[TypeScriptのDecoratorメモ](http://qiita.com/Quramy/items/e3a43bb1734b8a7331e8)と先を越されてしまいました。
とはいえ自分なりに試してみた知見について書いておきます。
**2015.04.09現在の内容です**

# Decoratorsとは
現在[ES7の仕様として検討されている](https://github.com/wycats/javascript-decorators)仕組みです。
クラスやメソッド、オブジェクトリテラル内のメソッドなど名前の前に`@hogehoge`と書いておくことで、別途定義した`hogehoge`という関数が呼び出され、ざっくり言えば対象を変更することができる。

TypeScriptではコンパイル時に`--target ES5`もしくは`--target ES6`を要求します。
その理由はこの後例で見せますが`Object.defineProperty`を利用しているためで、この`Object.defineProperty`については[Object.defineProper | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)もしくは@vvakameさんの「[TypeScriptリファレンス ](http://www.amazon.co.jp/gp/product/484433588X/ref=as_li_ss_tl?ie=UTF8&camp=247&creative=7399&creativeASIN=484433588X&linkCode=as2&tag=nana4gonta-22)」にも記述があるので、参考までに。

# サンプルコード
Decoratorsのサンプルとしてこんなコードを書いてみました。

```ts:decorators.ts
@decorateClass
class Greeter {
  @decorateProperty
  message: string;

  @decorateMethod
  hello(name:string = "JavaScript") {
    return `Hello, ${name}`;
  }
}

function decorateClass(target) {
  console.log(target);
  return target
}

function decorateMethod(target, name, descriptor) {
  console.log(`name: ${name}`);
  console.log(target);
  console.log(descriptor);
  return descriptor;
}

function decorateProperty(target, name) {
  console.log(`name: ${name}`);
  console.log(target);
  return target;
}

let greeter = new Greeter();
console.log(greeter.hello("TypeScript"));

```

そして変換されたあとのJavaScriptがこちら。

```js:decorators.js
var __decorate = this.__decorate || function (decorators, target, key, value) {
    var kind = typeof (arguments.length == 2 ? value = target : value);
    for (var i = decorators.length - 1; i >= 0; --i) {
        var decorator = decorators[i];
        switch (kind) {
            case "function": value = decorator(value) || value; break;
            case "number": decorator(target, key, value); break;
            case "undefined": decorator(target, key); break;
            case "object": value = decorator(target, key, value) || value; break;
        }
    }
    return value;
};
var Greeter = (function () {
    function Greeter() {
    }
    Greeter.prototype.hello = function (name) {
        if (name === void 0) { name = "JavaScript"; }
        return "Hello, " + name;
    };
    __decorate([decorateProperty], Greeter.prototype, "message");
    Object.defineProperty(Greeter.prototype, "hello", __decorate([decorateMethod], Greeter.prototype, "hello", Object.getOwnPropertyDescriptor(Greeter.prototype, "hello")));
    Greeter = __decorate([decorateClass], Greeter);
    return Greeter;
})();
function decorateClass(target) {
    console.log(target);
    return target;
}
function decorateMethod(target, name, descriptor) {
    console.log("name: " + name);
    console.log(target);
    console.log(descriptor);
    return descriptor;
}
function decorateProperty(target, name) {
    console.log("name: " + name);
    console.log(target);
    return target;
}
var greeter = new Greeter();
console.log(greeter.hello("TypeScript"));
```

そしてnodeでの実行結果がこちら

```bash
$ node decorators.js
name: message
{ hello: [Function] }
name: hello
{ hello: [Function] }
{ value: [Function],
  writable: true,
  enumerable: true,
  configurable: true }
[Function: Greeter]
Hello, TypeScript
```
# Decoratorsの実行部分で何をしているのか
## プロパティ部分
```
__decorate([decorateProperty], Greeter.prototype, "message");
```
`kind`では`value`を参照するが、第4引数が渡されていないため`undefined`となり、対象のDecoratorsが実行される。
Decoratorsに渡された`target`は`Greeter.prototype`を指しているので、上記のような実行結果となる。

## メソッド部分
```
Object.defineProperty(Greeter.prototype, "hello", __decorate([decorateMethod], Greeter.prototype, "hello", Object.getOwnPropertyDescriptor(Greeter.prototype, "hello")));
```

1. `Object.getOwnPropertyDescriptor`で対象のプロパティディスクリプタを取得
2. `__decorate`には引数を4つ渡しており、`kind`はプロパティディスクリプタを渡している`value`を見て`object`と判定される
3. `object`に合わせた引数が渡され、Decoratorsが実行される
4. `__decorate`でプロパティディスクリプタが返ってくるので、プロパティを再定義する

以上がメソッドについてのDecoratorsであり、その実行結果である。

## クラス部分
```
Greeter = __decorate([decorateClass], Greeter);
```

`kind`の取得部分で、引数が2つであるため`kind`は`target`である`Greeter`となる。
これは`Function`であるため対象のDecoratorsが実行される。
Decoratorsに渡された`value`は`Greeter`を指しているので、上記のような実行結果となる。


# ざっくりまとめ
ここまでの結果からとりあえず分かること
1. プロパティよりあとにクラスのDecoratorsが評価される
2. 生成された`__decorate`が`decorators`をfor文でループさせている
3. 最終的に型が合えば`decorators`としての関数はコンパイルが通る
4. 現状`Object.defineProperty`周りの知識は必要

## プロパティよりあとにクラスのDecoratorsが評価される
これは当たり前ですが、プロパティに対してのDecoratorsを評価したあとでなければ意味がないからですね。
例えばクラスに対してのDecoratorsが先に実行されてprototypeからプロパティが削除されたら、プロパティに対してのDecoratorsが動作しなくなります。

## 生成された`__decorate`が`decorators`をfor文でループさせている
これは1つのクラス、プロパティに対して複数のDecoratorsが渡される可能性を考慮しているということです。
試しに以下のようにコードを変更してみます(一部抜粋)

```ts
@decorateClass
@decorateClass
class Greeter {
  @decorateMethod
  hello(name:string = "JavaScript") {
    return `Hello, ${name}`;
  }
}
```
```js
var Greeter = (function () {
    function Greeter() {
    }
    Greeter.prototype.hello = function (name) {
        if (name === void 0) { name = "JavaScript"; }
        return "Hello, " + name;
    };
    Object.defineProperty(Greeter.prototype, "hello", __decorate([decorateMethod], Greeter.prototype, "hello", Object.getOwnPropertyDescriptor(Greeter.prototype, "hello")));
    Greeter = __decorate([decorateClass, decorateClass], Greeter);
    return Greeter;
})();
```

クラスに対しての`__decorate`の呼び出し部分で、第一引数である`decorators`の配列の要素が増えていることがわかります。
ちょっと夢が広がる感じがしませんか？
何をするのがいいのか見つけられていませんが。

## 最終的に型が合えば`decorators`としての関数はコンパイルが通る
TypeScriptはlib.d.tsにあるDecoratorの型定義に合わなければコンパイルエラーになるので心配はあまりないですが、Babelとかのトランスパイラーは変なものが返ってきてないかチェックは大丈夫なんでしょうか。。。
ES7 Proposalということは未来ではこの`Object.defineProperty`している部分等はブラウザが解釈することになり、Decoratorsとしての関数を定義するだけになるはずですが、返り値間違いの実行時エラーは怖いですね。

## 現状`Object.defineProperty`周りの知識は必要
これは個人的に思っただけですが、@vvakameさんの[AngularとDecoratorsの例](https://gist.github.com/vvakame/29a0f26c9b64877354c6)のような単純なDecoratorsなら難しくはないと思われます。
しかしDecoratorsで上手にプロパティの制御を行いたいといった場合には覚えておいて損はないと思いました。

他にもKnockoutJSの`ko.observable`とか、`Object.observe`の処理の代替とかもできそうで今後に期待ですね。


# 参考資料
- [wycats/javascript-decorators](https://github.com/wycats/javascript-decorators)
- [Decorators · Issue #2249 · Microsoft/TypeScript](https://github.com/Microsoft/TypeScript/issues/2249)
- [TypeScriptのDecoratorメモ - Qiita](http://qiita.com/Quramy/items/e3a43bb1734b8a7331e8)