TypeScriptで定数を定める

TypeScriptでは、書き替えのできない定数を定める構文がいくつかあります。それらの違いを、簡単なサンプルコードで比べてみましょう。

[追記: 2018年9月2日] オブジェクトのプロパティの書き替え、および`ReadonlyArray<T>`型についての説明を補いました。

# 01 const宣言で定数を定める
変数を`const`宣言すると、あとから値が書き替えられません。つまり、定数が定められます(「TypeScript: 変数の宣言」03「[const宣言](http://fumiononaka.com/Business/html5/FN1701010.html#sec03)」参照)。つぎの関数(getAngle())は、引数のxy座標がx軸正方向となす角度をラジアンで返します。定数(RAD_TO_DEG)には、ラジアンから度数への変換比率を定めました。座標(1, $\sqrt{3}$)とx軸との角度は60度になります(三角定規の2辺の比率です)。

```ts
const RAD_TO_DEG: number = 180 / Math.PI;
// RAD_TO_DEG = 1;  // const宣言した値は書き替えられない
function getAngle(x: number, y: number): number {
	return Math.atan2(y, x);
}
console.log(getAngle(1, Math.sqrt(3)) * RAD_TO_DEG);  // 59.99999999999999
```

ただし、気をつけなければならないのは、`const`定数は代入した値が上書きできないだけだということです。オブジェクトを変更しなけければ、プロパティ値は書き替えられます。

```js
const ZERO_POINT: {x: number, y: number} = {x: 0, y: 0};
// ZERO_POINT = {x: 100, y: 0};  // const宣言した値は上書きできない
ZERO_POINT.x = 100;  // プロパティ値は変えられる
console.log(ZERO_POINT);  // {x: 100, y: 0}
```


# 02 クラスに読み取り専用のプロパティを定める
`const`宣言は、クラスのプロパティには使えません。代わりに、`readonly`修飾子が備わりました(「[TypeScript 2.0のreadonly修飾子](http://fumiononaka.com/Business/html5/FN1610004.html)」参照)。プロパティが読み取り専用に定められます。

```ts
class Point {
	static readonly RAD_TO_DEG: number = 180 / Math.PI;
	constructor(public x: number, public y: number) {}
	getAngle(): number {
		return Math.atan2(this.y, this.x);
	}
}
const point: Point = new Point(1, Math.sqrt(3));
// Point.RAD_TO_DEG = 1;  // 読み取り専用なので書き替えられないというエラー
console.log(point.getAngle() * Point.RAD_TO_DEG);  // 59.99999999999999
```

ただし、`readonly`修飾子についても、`const`宣言と同じく、オブジェクトのプロパティ値は変えられます。

```js
class Point {

	static readonly ORIGIN = {x: 0, y: 0} as Point;

}
// Point.ORIGIN = new Point(0, 100);  // 読み取り専用なので書き替えられないというエラー
Point.ORIGIN.x = 100;  // プロパティ値は変えられる
console.log(Point.ORIGIN);  // {x: 100, y: 0}
```

配列については、`ReadonlyArray<T>`という書き替えのできない型が備わっています(「[Readonly properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#readonly-properties)」参照)。この型づけがされると配列アクセス演算子`[]`による要素の書き替えも、インスタンス自身を変更するメソッドの呼び出しもできません。インスタンスを書き替えるメソッドは除かれてしまうからです。

```js
class Point {

	static ZERO_ARRAY: ReadonlyArray<number> = [0, 0];

}
// Point.ZERO_ARRAY[0] = 100;  // 読み取り専用なので書き替えられないというエラー
// Point.ZERO_ARRAY.push(100);  // メソッドが存在しないというエラー
```


# 03 getアクセサで読み取りのみ定める
TypeScriptは、`get`/`set`アクセサでメソッドをプロパティのように扱えます(「TypeScript: クラス」06「[アクセサでプロパティのようにメソッドを扱う](http://fumiononaka.com/Business/html5/FN1703001.html#sec06)」参照)。`get`アクセサのみ定めて`set`アクセサを加えなければ、読み取り専用プロパティになります。

```ts
class Point {
	constructor(public x: number, public y: number) {}
	static get RAD_TO_DEG(): number {
		return 180 / Math.PI;
	}
	getAngle(): number {
		return Math.atan2(this.y, this.x);
	}
}
const point: Point = new Point(1, Math.sqrt(3));
// Point.RAD_TO_DEG = 1;  // 読み取り専用なので書き替えられないというエラー
console.log(point.getAngle() * Point.RAD_TO_DEG);  // 59.99999999999999
```

#04 readonly修飾子とgetアクセサの違い
`readonly`修飾子でも`get`アクセサでも、同じように定数が定められました。けれど、コンパイルされるJavaScriptコードの実装は違います。`readonly`修飾子の場合、JavaScriptには読み取り専用の修飾子がないので、プロパティはただクラスに加えられるだけです。したがって、実行時であれば値が変更できます(図001)。`ReadonlyArray<T>`型も、実行時には書き替えが可能です。

```ts
Point.RAD_TO_DEG = 180 / Math.PI;
```

### 図001■ readonly修飾子を与えたプロパティは実行時に書き替えできる
![図001](https://qiita-image-store.s3.amazonaws.com/0/128949/899f13e6-d65d-00cc-a340-5b7e89528cd7.png)

`get`アクセサはECMAScript 5.1から備わった[get構文](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/get)にコンパイルされます。そのため、返す値は実行時も書き替えできません(図002)。なお、`get`/`set`アクセサは、ECMAScript 5以降の設定にしないと使えないことにご注意ください。

### 図002■getアクセサの返す値は実行時に書き替えできない
![図002](https://qiita-image-store.s3.amazonaws.com/0/128949/c52c5d1a-d0a7-afa2-7f8a-2394ec98619e.png)

コンパイルされるJavaScriptコードは、ECMAScript 5とECMAScript 2015(ECMAScript 6)とではつぎのように異なります。

```js
// ECMAScript 5
Object.defineProperty(Point, "RAD_TO_DEG", {
	get: function () {
		return 180 / Math.PI;
	},
	enumerable: true,
	configurable: true
});
```

```js
// ECMAScript 2015
class Point {

	static get RAD_TO_DEG() {
		return 180 / Math.PI;
	}

}
```