一.クラスメンバー
TypeScript におけるクラスの定義は [ES6 Class 仕様](/articles/class-es6 笔记 10/) と一致し、静的プロパティ、インスタンスプロパティ、アクセサなどをすべてサポートします:
class Grid {
static origin = {x: 0, y: 0};
}
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
ただし 2 点に注意が必要です:
-
ES3 は getter/setter をサポートしていないため、ES5+ にコンパイル設定する必要があります
-
getter のみで setter がないプロパティは自動的に
readonlyと推論されます(メンバー修飾子の 1 つ)
二.メンバー修飾子
アクセス制御修飾子
3 つのアクセス制御修飾子をサポートします:
-
public:クラスのメンバー属性/メソッドはデフォルトですべて
publicで、アクセス制限はありません -
private:そのクラス宣言の外部からそのメンバーにアクセスできません(
this.xxxでプライベートメンバーにアクセスできないなど) -
protected:
privateと類似していますが、派生クラスでも保護されたメンバーにアクセスできます
例えば:
class Animal {
// プライベートメンバー属性
private name: string;
// 保護されたメンバー属性
protected ancestor: string;
// 書かない場合、デフォルトで public
constructor(theName: string) { this.name = theName; }
// public メンバーメソッド
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
注意、これらのアクセス制御はすべてコンパイル時の制限であり、ランタイムでは強制的なチェックを行いません。TypeScript の設計原則に合致します:
コンパイル産物にランタイムオーバーヘッドを追加しない
また、クラスメンバーのアクセス可能性もタイプチェックの一部であり、private/protected 修飾子はダックタイプを打破します。例えば:
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
}
class Person {
name: string;
constructor(theName: string) { this.name = theName; }
}
// 正しい:ダックタイプ(似ていれば)
let person: Animal = new Person('Sam');
class Plant {
// プライベートメンバー name
private name: string;
constructor(theName: string) { this.name = theName; }
}
// エラー:name 属性のアクセス可能性が一致しない(似ていてもダメ、アクセス可能性が異なる)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');
P.S.特別に、protected constructor はそのクラスが直接インスタンス化できないことを示しますが、継承はサポートします
readonly 修飾子
readonly 修飾子を通じて属性を読み取り専用に宣言でき、宣言時またはコンストラクタ内でのみ赋值できます。例えば:
class Octopus {
readonly name: string;
public 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.
P.S.もちろん、readonly とアクセス制御修飾子は矛盾せず、同じ属性に作用できます
パラメータ属性
コンストラクタ内で初期化される属性に対して:
class Octopus {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
簡略化方式 があります:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
その中で、name はパラメータ属性と呼ばれ、コンストラクタの形式パラメータ名の前に private/protected/public/readonly 修飾子を付けて宣言します
三.継承
class A extends B {
//...
}
Babel 変換と類似しています(明確にするため、重要な部分のみ抜粋):
function _inherits(subClass, superClass) {
// 親クラスのプロトタイプ属性を継承
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// 親クラスの静的属性を継承
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
// サブクラスコンストラクタ内で親クラスのインスタンス属性を継承
function A() {
// 親クラスコンストラクタを通じてサブクラスインスタンス this に親クラスインスタンス属性を追加
return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}
TypeScript における Class 継承もプロトタイプベースの継承にコンパイル置換されます。以下の通り:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
// 親クラスの静的属性を継承
extendStatics(d, b);
function __() { this.constructor = d; }
// 親クラスのプロトタイプ属性、及びインスタンス属性を継承
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// __extends(A, B);
両者は大同小異で、実装から見ると、TypeScript コンパイル産物はより堅牢です。その目標は:
ES3+ をサポートするすべてのホスト環境で実行
P.S.比較面白いのは静的属性の継承で、詳細は [一。どのように静的属性を継承するか?](/articles/class 继承-es6 笔记 12/#articleHeader2) を参照
四.抽象クラス
TypeScript にも抽象クラスの概念があります:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
抽象クラスには実装付きの具体的メソッド(move など)もあれば、宣言のみで実装しない抽象メソッド(makeSound など)もありますが、サブクラスはこれらのメソッドを実装する必要があります:
class Cat extends Animal {
makeSound() {
console.log('meow meow meow');
}
}
もう 1 つの類似した概念はインターフェースで、両者の違いはインターフェースでは「抽象メソッド」のみを定義できることです(abstract キーワードはなく、正確にはメソッドシグネチャ)。例えば:
interface Animal {
// abstract makeSound(): void; と比較
makeSound(): void;
}
// class Cat extends Animal と比較
class Cat implements Animal {
makeSound() {
console.log('meow meow meow');
}
}
五.クラスとタイプ
クラスを宣言すると同時に、そのクラスインスタンスのタイプも宣言します。例えば:
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
その中で、インスタンス greeter は Greeter タイプです。つまり、Class 宣言はタイプ意味を持ちます:
-
そのクラスインスタンスのタイプ:
Greeter -
クラス自身のタイプ:
typeof Greeter
実際には、クラス自身のタイプは静的属性、インスタンス属性、コンストラクタ、プロトタイプメソッドなどの特徴を制約します。例えば:
class GreeterDuck {
// クラス自身のタイプ制約
static standardGreeting = 'Hi';
greeting: string;
constructor(message: string) { }
greet() { return 'there'; }
// 多くてもよく少なくてもダメ(ダックタイプ)
sayHello() { /*...*/ }
}
let greeterType: typeof Greeter = GreeterDuck;
さらに進んで:
// Class タイプから Interface を拡張
interface HelloGreeter extends Greeter {
sayHello(): void;
}
let hGreeter: HelloGreeter = new GreeterDuck('world');
その通りです。クラスはタイプ意味を持つため、インターフェースはクラスから継承できます
コメントはまだありません