一.類成員
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(成員修飾符之一)
二.成員修飾符
訪問控制修飾符
支持 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');
}
}
另一個相似的概念是接口,二者區別在於接口中只能定義「抽象方法」(沒有 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 是 Greetr 類型的,也就是說,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');
沒錯,因為類具有類型含義,所以接口能夠繼承自類
暫無評論,快來發表你的看法吧