クラス

JavaScriptにもクラスの概念は存在します。TypeScriptでのクラスは主にJavaScriptのクラスをより型安全に拡張されています。

クラスに関する機能

簡易な一覧ですが、型に関する機能が拡張されていることが分かると思います。

機能名

JavaScript

TypeScript

クラス機能

継承

super

抽象クラス(abstract)

アクセス修飾子

インターフェース

ジェネリクス

クラスに関わらずTypeScriptの特徴となるのはコーディング中に型が不適切な場合はコンパイルエラーを教えてくれることです。JavaScriptの場合は基本的に実行を行い不正な型が発生した場合に型エラーを知ることになります。TypeScriptコードに問題がなく型安全と判断された場合に限りコンパイルを行う事ができます。また出力されたJavaScriptファイルには型情報は除かれます。

書式

一般的なクラスの書式を日本語で説明するとこののようになります。

class クラス名 {
プロパティ;
constructor(){}
メソッド(){
処理
}
}

下記は実際のコードのサンプルです。

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

多くのクラスはインスタンス化(new)し、メソッドやプロパティを呼ぶために使われます。

let greeter = new Greeter("world");
console.log(greeter.greet()); // Hello, world

継承

オブジェクト指向の基本的である継承をサポートしています。継承する場合はextendsキーワードを使用します。

class Food { }
class Meat extends Food { }
class Fish extends Food { }
class Vegetables extends Food { }

この場合Foodは親、MeatFishVegetablesは子の関係になります。

親は「基本クラス」、「スーパークラス」。子は「派生クラス」、「サブクラス」などと呼ばれます。本章では親を「スーパークラス」、子を「サブクラス」として呼び方を統一します。

サブクラスはスーパークラスのプロパティ、メソッドを継承します。

class Food {
constructor(protected name: string , protected calorie: number) { }
showDebug() {
console.log(`name = ${this.name} `);
console.log(`calorie = ${this.calorie}kcal `);
}
}
class Meat extends Food { }
let meat = new Meat('chicken' , 100);
meat.showDebug(); // スーパークラスのメソッドが使用できる。

abstract

abstractは抽象クラスを作成する時に宣言します。抽象クラスとは直接インスタンス化(new)することができず、必ずスーパークラスとして利用することを保証するものです。抽象クラス内のメソッドにもabstract宣言を行うことができます。interfaceと似てサブクラスは抽象メソッドを実装する必要があります。

Foodクラスに抽象クラスに変更し、"要冷蔵"メソッドkeepRefrigerated()を抽象メソッドとして追加するとMeatクラスでエラーが発生します。これはMeatクラスにkeepRefrigeratedメソッドが実装されていないからです。

abstract class Food {
constructor(protected name: string , protected calorie: number) { }
showDebug() {
console.log(`name = ${this.name} `);
console.log(`calorie = ${this.calorie}kcal `);
}
abstract keepRefrigerated(): boolean;
}
class Meat extends Food { } // エラー:非抽象クラス 'Meat' はクラス 'Food' からの継承抽象メンバー 'keepRefrigerated' を実装しません。

keepRefrigeratedメソッドを実装することによりエラーはなくなります。

class Meat extends Food {
keepRefrigerated(): boolean {
return true;
}
}

プロパティとメソッドに対するアクセス修飾子

アクセス修飾子

説明

(宣言なし)

publicと同等

public

どこからもアクセス可能

protected

自身のクラスとサブクラスからアクセス可能

private

自身のクラスのみアクセス可能

アクセス修飾子を省略した場合はpublicになります。

アクセス修飾子は、プロパティ、コンストラクター、メソッドに宣言に宣言することができます。

public

publicアクセス修飾子はどこからもアクセス可能です。

class Animal {
public name: string; // プロパティにpublicアクセス修飾子
public constructor(theName: string) { this.name = theName; } // コンストラクターにpublicアクセス修飾子
public move(distanceInMeters: number) { // メソッドにpublicアクセス修飾子
console.log(`${this.name} moved ${distanceInMeters}m.`); // publicアクセス修飾子である`this.name`を使用することが可能
}
}

gorillaを実装し、動作を確認してみます。

const gorilla = new Animal('ゴリラ');
gorilla.move(10); // ゴリラ moved 10m.
gorilla.name = 'ゴリラゴリラ';
gorilla.move(20); // ゴリラゴリラ moved 20m.

nameプロパティはpublic宣言されているため、インスタンスされた変数(gorilla)からの読み書きが可能になっています。「ゴリラ」から「ゴリラゴリラ」に変更することができます。

protected

protectedアクセス修飾子は自身のクラスとサブクラスからアクセス可能です。

Animalクラスmoveメソッドのアクセス修飾子をpublicからprotectedに変更しエラーを出してみます。

class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
protected move(distanceInMeters: number) { // `public`から`protected`に変更
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
const gorilla = new Animal('ゴリラ');
gorilla.move(10); // error TS2339: Property 'move' does not exist on type 'Animal'.

gorilla.moveメソッドはprotected宣言されているため、自身のクラスとサブクラスのみアクセスとなります。つまりインスタンスされたgorillaからはアクセスが拒否され、コンパイルエラーが発生します。

protectedで保護されたmoveメソッドを新たに実装し、早く動く10倍早く動くゴリラを作ってみます。

class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
protected move(distanceInMeters: number) { // `public`から`protected`に変更
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Gorilla extends Animal {
move(distanceInMeters: number) {
super.move(distanceInMeters * 10);
}
}
const gorilla = new Gorilla('早いゴリラ');
gorilla.move(10); // 早いゴリラ moved 100m.

Animalスーパークラスを持つGorillaクラスを定義しmoveを実装しています。Gorillaクラスのmoveメソッド内でsuperキーワードを利用してスーパークラスのmoveメソッドを呼び出しています。

private

privateアクセス修飾子は自身のクラスのみアクセス可能です。

protected moveprivate moveに変更してみます。privateに変更されたことによりGorillaクラスのsuper.moveにアクセスすることが許されずエラーとなります。

class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
private move(distanceInMeters: number) { // `public`から`protected`に変更
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Gorilla extends Animal {
move(distanceInMeters: number) {
super.move(distanceInMeters * 10); // Property 'move' is private and only accessible within class 'Animal'.
}
}

privateメソッドの多くの使い方としては、自身のクラス内の長いコードを機能別に分ける時に利用します。

コンストラクターの引数のアクセス修飾子

メソッドの引数にはアクセス修飾子を設定することはできませんがコンストラクターは特別です。

引数に対してアクセス修飾子を宣言した場合はこのようにな意味になります。

アクセス修飾子

説明

(宣言なし)

constructorメソッド内のみアクセス可能

public

自身のクラス内、継承クラス、インスタンス化されたクラスのどれからでもアクセス可能

protected

自身のクラス、継承クラスからアクセス可能

private

自身のクラスのみアクセス可能

ConstructorInAccessModifierクラスとConstructorOutAccessModifierクラスの2つを定義しました。

2つのクラスの違いはコンストラクターにアクセス修飾子を定義しているかどうかだけで機能は全く同じです。

// example.ts
class ConstructorInAccessModifier {
constructor(
arg0: number,
public arg1: number,
protected arg2: number,
private arg3: number
) {
console.log({ arg0, arg1, arg2, arg3 });
}
}
class ConstructorOutAccessModifier {
public arg1: number;
protected arg2: number;
private arg3: number;
constructor(
arg0: number,
arg1: number,
arg2: number,
arg3: number
) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
console.log({ arg0, arg1, arg2, arg3 });
}
}

コンパイル後のJavaScriptファイルを見てみると同一の機能を持つことが確認することができます。

// example.js
class ConstructorInAccessModifier {
constructor(arg0, arg1, arg2, arg3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
console.log({ arg0, arg1, arg2, arg3 });
}
}
class ConstructorOutAccessModifier {
constructor(arg0, arg1, arg2, arg3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
console.log({ arg0, arg1, arg2, arg3 });
}
}

TypeScriptで記述する際は各アクセス修飾子のスコープ機能が有効になるため、インスタンスからのアクセスが可能なプロパティはpublic宣言されたarg1のみが有効になります。

// example.ts
let InAccess = new ConstructorInAccessModifier(1, 2, 3, 4);
InAccess.arg0; // エラー プロパティ 'arg0' は型 'ConstructorInAccessModifier' に存在しません。ts(2339)
InAccess.arg1;
InAccess.arg2; // エラー プロパティ 'arg2' は型 'ConstructorInAccessModifier' に存在しません。ts(2339)
InAccess.arg3; // エラー プロパティ 'arg3' は型 'ConstructorInAccessModifier' に存在しません。ts(2339)
let outAccess = new ConstructorOutAccessModifier(1, 2, 3, 4);
outAccess.arg0; // エラー プロパティ 'arg0' は型 'ConstructorOutAccessModifier' に存在しません。ts(2339)
outAccess.arg1;
outAccess.arg2; // エラー プロパティ 'arg2' は型 'ConstructorOutAccessModifier' に存在しません。ts(2339)
outAccess.arg3; // エラー プロパティ 'arg3' は型 'ConstructorOutAccessModifier' に存在しません。ts(2339)

つまり、コンストラクターの引数のアクセス修飾子はプロパティ宣言の省略をしてくれるだけにすぎません。

Readonly修飾子(Readonly modifier)

readonly修飾子を利用してプロパティを読み取り専用にすることができます。

readonlyを宣言したプロパティは変数宣言時、またはコンストラクター内で初期化する必要があります。

class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.

Getter/Setter

プロパティへのインターセプター(参照・代入・監視などの意味)としGetter/Setterがあります。

記述方法のサンプルは下記のようになります。

class Human {
private _name: string;
// Getter宣言
get name(): string {
return this._name;
}
// Setter宣言
set name(name: string) {
this._name = name;
}
}
const human = new Human();
// Setterを利用
human.name = `田中太郎`;
// Getterを利用
console.log(human.name); // 田中太郎

メソッドと違い、getter/setterを呼ぶ場合は()は不要です。

// Getter
console.log(human.name); // 正しいGetterの使用方法
console.log(human.name()); // エラー :human.name is not a function
// Setter
human.name = '田中太郎'; // 正しいSetterの使用方法
human.name('田中太郎'); // エラー :human.name is not a function

Getter

Getterの記述方法を日本語で表すと下記のようになります。

get 名前():{
必要ならば処理();
return 戻り値;
}

Getterにはに引数の指定することはできません。また戻り値を必ず指定する必要があります。

Setter

Setterの記述方法を日本語で表すと下記のようになります。

set 名前(変数 :) {
必要ならば処理();
保存処理();
}

引数を必ず1つする必要があります。また戻り値を指定することはできません。