TypeScriptでは、書き替えのできない定数を定める構文がいくつかあります。それらの違いを、簡単なサンプルコードで比べてみましょう。
[追記: 2018年9月2日] オブジェクトのプロパティの書き替え、およびReadonlyArray<T>
型についての説明を補いました。
01 const宣言で定数を定める
変数をconst
宣言すると、あとから値が書き替えられません。つまり、定数が定められます(「TypeScript: 変数の宣言」03「const宣言」参照)。つぎの関数(getAngle())は、引数のxy座標がx軸正方向となす角度をラジアンで返します。定数(RAD_TO_DEG)には、ラジアンから度数への変換比率を定めました。座標(1, $\sqrt{3}$)とx軸との角度は60度になります(三角定規の2辺の比率です)。
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
定数は代入した値が上書きできないだけだということです。オブジェクトを変更しなけければ、プロパティ値は書き替えられます。
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修飾子」参照)。プロパティが読み取り専用に定められます。
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
宣言と同じく、オブジェクトのプロパティ値は変えられます。
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」参照)。この型づけがされると配列アクセス演算子[]
による要素の書き替えも、インスタンス自身を変更するメソッドの呼び出しもできません。インスタンスを書き替えるメソッドは除かれてしまうからです。
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「アクセサでプロパティのようにメソッドを扱う」参照)。get
アクセサのみ定めてset
アクセサを加えなければ、読み取り専用プロパティになります。
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>
型も、実行時には書き替えが可能です。
Point.RAD_TO_DEG = 180 / Math.PI;
図001■ readonly修飾子を与えたプロパティは実行時に書き替えできる
get
アクセサはECMAScript 5.1から備わったget構文にコンパイルされます。そのため、返す値は実行時も書き替えできません(図002)。なお、get
/set
アクセサは、ECMAScript 5以降の設定にしないと使えないことにご注意ください。
図002■getアクセサの返す値は実行時に書き替えできない
コンパイルされるJavaScriptコードは、ECMAScript 5とECMAScript 2015(ECMAScript 6)とではつぎのように異なります。
// ECMAScript 5
Object.defineProperty(Point, "RAD_TO_DEG", {
get: function () {
return 180 / Math.PI;
},
enumerable: true,
configurable: true
});
// ECMAScript 2015
class Point {
static get RAD_TO_DEG() {
return 180 / Math.PI;
}
}