JavaScriptにおいて、クラスを第一級(first class)のものとして持つことが重要である理由は次の通りです:
- クラスが提供する便利な構造の抽象化
- それぞれのフレームワーク(emberjs、reactjsなど)がそれぞれ独自のクラス構文を提供するのではなく、一貫したクラス構文を提供する
- オブジェクト指向の開発者にとっての親和性
TypeScriptでは、ブラウザの互換性を気にすることなくclass
を使うことができます。ここに、Pointという簡単なクラスの例を示します:
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
add(point: Point) {
return new Point(this.x + point.x, this.y + point.y);
}
}
var p1 = new Point(0, 10);
var p2 = new Point(10, 20);
var p3 = p1.add(p2); // {x:10,y:30}
このクラスをコンパイルすると、古いブラウザ(ES5)で動作する次のJavaScriptを生成します。
var Point = (function () {
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function (point) {
return new Point(this.x + point.x, this.y + point.y);
};
return Point;
})();
これは慣用的に使われてきた従来のJavaScriptのクラスのパターンです。今は第一級(first class)の言語の構成要素としてのクラスが利用できます。
TypeScriptにおけるクラスは(他の言語のように)extends
キーワードを使った 単一 継承をサポートします:
class Point3D extends Point {
z: number;
constructor(x: number, y: number, z: number) {
super(x, y);
this.z = z;
}
add(point: Point3D) {
var point2D = super.add(point);
return new Point3D(point2D.x, point2D.y, this.z + point.z);
}
}
クラスにコンストラクタがある場合、コンストラクタから親コンストラクタを呼び出さなければなりません(TypeScriptはこれをしていない場合はエラーを表示します)。これにより、this
のプロパティが正しく設定されます。super
を呼び出した後、コンストラクタで必要な処理を追加することができます(例えば、他のプロパティz
を追加します)。
親のメンバ関数をオーバーライドする(ここではadd
をオーバーライドします)場合でも、親クラスの機能を呼び出せることに注意してください(super.
構文を使います)。
TypeScriptクラスは、クラスの全インスタンスで共有されるstatic
なプロパティをサポートします。静的メンバを置き、アクセスする自然な場所はクラスそのものです。TypeScriptでは、static
がサポートされています。
class Something {
static instances = 0;
constructor() {
Something.instances++;
}
}
var s1 = new Something();
var s2 = new Something();
console.log(Something.instances); // 2
静的メンバと同様に静的関数も使用できます。
TypeScriptはアクセス修飾子としてpublic
、private
、protected
をサポートしています。これらはclass
メンバのアクセシビリティを次のように決定します:
アクセス可能な場所(accessible on) | public |
protected |
private |
---|---|---|---|
クラス(class) | yes | yes | yes |
子クラス(class children) | yes | yes | no |
クラスのインスタンス(class instances) | yes | no | no |
アクセス修飾子が指定されていない場合は、JavaScriptの便利な性質と同じように、暗黙的にpublic
となります🌹。
アクセス修飾子は、ランタイム上(生成されたJSの実行時)では何の影響もありませんが、間違った使い方をするとコンパイルエラーが出力されることに注意してください。それぞれの例を以下に示します:
class FooBase {
public x: number;
private y: number;
protected z: number;
}
// インスタンスにおける効果
var foo = new FooBase();
foo.x; // okay
foo.y; // ERROR : private
foo.z; // ERROR : protected
// サブクラスにおける効果
class FooChild extends FooBase {
constructor() {
super();
this.x; // okay
this.y; // ERROR: private
this.z; // okay
}
}
いつもと同じように、これらの修飾子はメンバ変数とメンバ関数の両方で利用可能です。
abstract
はアクセス修飾子の1つと考えることができます。上で述べた修飾子とは異なり、クラスのメンバと同様にclass
に対しても利用できるため、アクセス修飾子とは分けて説明します。abstract
修飾子が主に意味することは、その機能を親クラスに対して直接的に呼び出すことができず、子クラスがその具体的な機能を提供しなければならないということです。
- 抽象クラスを直接インスタンス化することはできません。その代わりに、
abstract class
を継承したclass
を作成しなければなりません
abstract class FooCommand {}
class BarCommand extends FooCommand {}
const fooCommand: FooCommand = new FooCommand(); // 抽象クラスのインスタンスは作成できません
const barCommand = new BarCommand(); // 抽象クラスを継承したクラスのインスタンスは作成できます
- 抽象メンバは直接アクセスできません。子クラスがその具体的な機能(実装)を提供しなくてはなりません
abstract class FooCommand {
abstract execute(): string;
}
class BarErrorCommand extends FooCommand {} // 'BarErrorCommand'は抽象メンバー'execute'を実装する必要があります
class BarCommand extends FooCommand {
execute() {
return `コマンドBarが実行されました`;
}
}
const barCommand = new BarCommand();
barCommand.execute(); // コマンドBarが実行されました
クラスは必ずコンストラクタを持っている必要はありません。例えば、以下は正しく動作します。
class Foo {}
var foo = new Foo();
以下のように、クラスのメンバを定義し、初期化することができます:
class Foo {
x: number;
constructor(x:number) {
this.x = x;
}
}
これはTypeScriptの省略形を使うことができるパターンです。コンストラクタ引数にアクセス修飾子を付けることができ、それが自動的にクラス内に宣言され、コンストラクタの引数によって値が初期化されます。なので、前の例は次のように書き直すことが可能です(public x:number
に注目してください):
class Foo {
constructor(public x:number) {
}
}
これはTypeScript(及びES7以降)でサポートされている気の利いた機能です。クラスのコンストラクタの外側でクラスのメンバ変数を初期化できます。デフォルト値を指定するのに便利です(members = []
に注目してください)。
class Foo {
members = []; // コンストラクタなしで直接メンバ変数を初期化できます
add(x) {
this.members.push(x);
}
}