TypeScript/ES7でクラス定数

ガチガチに固めた、静的な言語のようなクラスをJavaScript(TypeScript+ES6+ES7)で作ってみるシリーズ。

## これまでは

ES6ではそもそもクラスメンバーにデータプロパティを定義できない。ES7を先行対応しているTypeScript/Babelではデータプロパティは定義できるが`const`、つまり定数としては定義できない。

[以前書いた記事](http://qiita.com/ConquestArrow/items/494d798a4e0788c41a7c)ではgetterとして定義する方法、クラスではなくネームスペースで定義する方法を紹介した。

```javascript:getterでクラス定数っぽく
"use strict";
class A{
	static get PROP(){return 1;}
}
A.PROP;	//1
```

 * 上記の問題点：
	 * 書き方が冗長で定数っぽく見えない
	 * 上書きしていてもTypeScriptのコンパイル時チェックをすり抜ける
		 * ランタイムにはエラーになる

```javascript:TypeScriptならネームスペースで
"use strict";
namespace nsA{
	export const PROP = 1;
}
nsA.PROP;	//1
```

 * 上記の問題点:
	 * クラスと同名のネームスペースは定義できない
		 * クラス定数っぽくならない
	 * ランタイム時に上書きできてしまう
		 * ただの`var`になるので

## デコレータでクラス定数風に定義

ES7ではクラスにメンバープロパティを定義できる。それに加え、デコレータで定数っぽくすることもできる。

TypeScriptでやってみると以下。

```javascript:TypeScriptでクラス定数
class A{
	@readonly static PROP = 1;
}

A.PROP;	//1
A.PROP = 100;	//runtime error!
```

```javascript:デコレータの定義
function readonly(target: any, propertyKey: string | symbol){
	let p = target[propertyKey];
	Object.defineProperty(
		target,
		<string>propertyKey,
		{
			get:()=>p,
			enumerable:true
		}
	);
}
```

上記はgetterで擬似的に定数を指定しているだけなので、ランタイムエラーもgetterしかないよ、というようなエラーである。

```:エラー文はちょっと分かりにくい
TypeError: Cannot set property PROP of function A() {
    } which has only a getter
```

なので分かりやすいように以下のようにエラーを吐くようにしてみる。

```javascript:エラーを分かりやすく
function readonly(target: any, propertyKey: string | symbol){
	let p = target[propertyKey];
	
	let eSetter = (val:any)=>{
		throw TypeError(`Cannot set property ${propertyKey} of ${target} which is readonly.`);
	}
	
	Object.defineProperty(
		target,
		<string>propertyKey,
		{
			get:()=>p,
			set:eSetter,
			enumerable:true
		}
	);
}
```

コードのまとめ：[TypeScript PlayGround](http://www.typescriptlang.org/Playground#src=function%20readonly(target%3A%20any%2C%20propertyKey%3A%20string%20%7C%20symbol)%7B%0A%09let%20p%20%3D%20target%5BpropertyKey%5D%3B%0A%09%0A%09let%20eSetter%20%3D%20(val%3Aany)%3D%3E%7B%0A%09%09throw%20TypeError(%60Cannot%20set%20property%20%24%7BpropertyKey%7D%20of%20%24%7Btarget%7D%20which%20is%20readonly.%60)%3B%0A%09%7D%0A%09%0A%09Object.defineProperty(%0A%09%09target%2C%0A%09%09%3Cstring%3EpropertyKey%2C%0A%09%09%7B%0A%09%09%09get%3A()%3D%3Ep%2C%0A%09%09%09set%3AeSetter%2C%0A%09%09%09enumerable%3Atrue%2C%0A%09%09%09configurable%3Atrue%0A%09%09%7D%0A%09)%3B%0A%7D%0Aclass%20A%7B%0A%09%40readonly%20static%20PROP%20%3D%201%3B%0A%7D%0Aconsole.log(A.PROP)%3B%09%2F%2F1%0AA.PROP%20%3D%20100%3B%09%2F%2FUncaught%20TypeError%3A%20Cannot%20set%20property%20PROP%20of%20function%20A()%20%7B%7D%20which%20is%20readonly.) 

なお`const`は予約語なので関数名に使えないため`@const`というデコレータ名にはできなかった。`@const`とできればもっと分かりやすかったのだが。

## 注意点

 * TypeScriptのコンパイル時チェックでは分からない
 * 上記コードは残念ながら現時点のBabelだとエラーにならず、定数にならない
	 * [コード](https://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code=function%20readonly(target%2C%20propertyKey)%7B%0A%09let%20p%20%3D%20target%5BpropertyKey%5D%3B%0A%09%0A%09let%20eSetter%20%3D%20(val%3Aany)%3D%3E%7B%0A%09%09throw%20Error(%60Cannot%20set%20property%20%24%7BpropertyKey%7D%20of%20%24%7Btarget%7D%20which%20is%20readonly.%60)%3B%0A%09%7D%0A%09%0A%09Object.defineProperty(%0A%09%09target%2C%0A%09%09propertyKey%2C%0A%09%09%7B%0A%09%09%09get%3A()%3D%3Ep%2C%0A%09%09%09set%3AeSetter%2C%0A%09%09%09enumerable%3Atrue%2C%0A%09%09%09configurable%3Atrue%0A%09%09%7D%0A%09)%3B%0A%7D%0Aclass%20A%7B%0A%09%40readonly%20static%20PROP%20%3D%201%3B%0A%7D%0Aconsole.log(A.PROP)%3B%09%2F%2F1%0AA.PROP%20%3D%20100%3B%09%2F%2FUncaught%20TypeError%3A%20Cannot%20set%20property%20PROP%20of%20function%20A()%20%7B%7D%20which%20is%20readonly.%0A)
	 * 原因はデコレータの実現方法の違い
	 * なのである日突然TypeScriptでも動かないコードになる可能性がある