類(lèi)
介紹
傳統(tǒng)的 JavaScript 程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο蠓绞降某绦騿T來(lái)講就有些棘手,因?yàn)樗麄冇玫氖腔陬?lèi)的繼承并且對(duì)象是由類(lèi)構(gòu)建出來(lái)的。 從 ECMAScript 2015,也就是 ECMAScript 6 開(kāi)始,JavaScript 程序員將能夠使用基于類(lèi)的面向?qū)ο蟮姆绞健?使用 TypeScript,我們?cè)试S開(kāi)發(fā)者現(xiàn)在就使用這些特性,并且編譯后的 JavaScript 可以在所有主流瀏覽器和平臺(tái)上運(yùn)行,而不需要等到下個(gè) JavaScript 版本。
類(lèi)
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
繼承
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
這個(gè)例子展示了最基本的繼承:類(lèi)從基類(lèi)中繼承了屬性和方法。 這里, Dog 是一個(gè) 派生類(lèi),它派生自 Animal 基類(lèi),通過(guò) extends 關(guān)鍵字。 派生類(lèi)通常被稱(chēng)作 子類(lèi),基類(lèi)通常被稱(chēng)作 超類(lèi)。
公共,私有與受保護(hù)的修飾符
默認(rèn)為
在 TypeScript 里,成員都默認(rèn)為 public。你也可以明確的將一個(gè)成員標(biāo)記成 public。
class Animal {
public name: string;
public constructor(theName: string) {
this.name = theName;
}
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
理解
當(dāng)成員被標(biāo)記成 private 時(shí),它就不能在聲明它的類(lèi)的外部訪問(wèn)
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
new Animal("Cat").name; // 錯(cuò)誤: 'name' 是私有的.
理解
protected 修飾符與 private 修飾符的行為很相似,但有一點(diǎn)不同, protected 成員在派生類(lèi)中仍然可以訪問(wèn)
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 錯(cuò)誤
注意,我們不能在 Person 類(lèi)外使用 name,但是我們?nèi)匀豢梢酝ㄟ^(guò) Employee 類(lèi)的實(shí)例方法訪問(wèn),因?yàn)?Employee 是由 Person 派生而來(lái)的.
構(gòu)造函數(shù)也可以被標(biāo)記成 protected。 這意味著這個(gè)類(lèi)不能在包含它的類(lèi)外被實(shí)例化,但是能被繼承
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
// Employee 能夠繼承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 錯(cuò)誤: 'Person' 的構(gòu)造函數(shù)是被保護(hù)的.
readonly 修飾符
你可以使用 readonly 關(guān)鍵字將屬性設(shè)置為只讀的。 只讀屬性必須在聲明時(shí)或構(gòu)造函數(shù)里被初始化。
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"; // 錯(cuò)誤! name 是只讀的.
參數(shù)屬性
注意看我們是如何舍棄了 theName,僅在構(gòu)造函數(shù)里使用 readonly name: string 參數(shù)來(lái)創(chuàng)建和初始化 name 成員。 我們把聲明和賦值合并至一處
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}
參數(shù)屬性通過(guò)給構(gòu)造函數(shù)參數(shù)前面添加一個(gè)訪問(wèn)限定符來(lái)聲明。 使用 private 限定一個(gè)參數(shù)屬性會(huì)聲明并初始化一個(gè)私有成員;對(duì)于 public 和 protected 來(lái)說(shuō)也是一樣。
存取器
TypeScript 支持通過(guò) getters/setters 來(lái)截取對(duì)對(duì)象成員的訪問(wèn)。
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}
注意 只帶有 get 不帶有 set 的存取器自動(dòng)被推斷為 readonly
靜態(tài)屬性
實(shí)例成員:僅當(dāng)類(lèi)被實(shí)例化的時(shí)候才會(huì)被初始化的屬性
靜態(tài)成員:存在于類(lèi)本身上面而不是類(lèi)的實(shí)例上,關(guān)鍵字 <font color="#BF414A">static</font>
class Grid {
static origin = { x: 0, y: 0 };
calculateDistanceFromOrigin(point: { x: number; y: number }) {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale: number) {}
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));
抽象類(lèi)
抽象類(lèi)做為其它派生類(lèi)的基類(lèi)使用。 它們一般不會(huì)直接被實(shí)例化。 不同于接口,抽象類(lèi)可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。 關(guān)鍵字是用于定義抽象類(lèi)和在抽象類(lèi)內(nèi)部定義抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("roaming the earch...");
}
}
抽象類(lèi)中的抽象方法不包含具體實(shí)現(xiàn)并且必須在派生類(lèi)中實(shí)現(xiàn)。 抽象方法的語(yǔ)法與接口方法相似。 兩者都是定義方法簽名但不包含方法體。
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 必須在派生類(lèi)中實(shí)現(xiàn)
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // 在派生類(lèi)的構(gòu)造函數(shù)中必須調(diào)用 super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
let department: Department; // 允許創(chuàng)建一個(gè)對(duì)抽象類(lèi)型的引用
department = new Department(); // 錯(cuò)誤: 不能創(chuàng)建一個(gè)抽象類(lèi)的實(shí)例
department = new AccountingDepartment(); // 允許對(duì)一個(gè)抽象子類(lèi)進(jìn)行實(shí)例化和賦值
department.printName();
department.printMeeting();
department.generateReports(); // 錯(cuò)誤: 方法在聲明的抽象類(lèi)中不存在
高級(jí)技巧
構(gòu)造函數(shù)
當(dāng)你在 TypeScript 里聲明了一個(gè)類(lèi)的時(shí)候,實(shí)際上同時(shí)聲明了很多東西
1.類(lèi)的 實(shí)例 的類(lèi)型。
我們寫(xiě)了 let greeter: Greeter,意思是 Greeter 類(lèi)的實(shí)例的類(lèi)型是 Greeter
2.我們也創(chuàng)建了一個(gè)叫做 構(gòu)造函數(shù) 的值。
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
上面的代碼里, let Greeter 將被賦值為構(gòu)造函數(shù)。 當(dāng)我們調(diào)用 new 并執(zhí)行了這個(gè)函數(shù)后,便會(huì)得到一個(gè)類(lèi)的實(shí)例。 這個(gè)構(gòu)造函數(shù)也包含了類(lèi)的所有靜態(tài)屬性。 換個(gè)角度說(shuō),我們可以認(rèn)為類(lèi)具有 實(shí)例部分與 靜態(tài)部分這兩個(gè)部分。
讓我們稍微改寫(xiě)一下這個(gè)例子
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
這個(gè)例子里, greeter1 與之前看到的一樣。 我們實(shí)例化 Greeter 類(lèi),并使用這個(gè)對(duì)象。 與我們之前看到的一樣。
再之后,我們直接使用類(lèi)。 我們創(chuàng)建了一個(gè)叫做 greeterMaker 的變量。 這個(gè)變量保存了這個(gè)類(lèi)或者說(shuō)保存了類(lèi)構(gòu)造函數(shù)。 然后我們使用 typeof Greeter,意思是取 Greeter 類(lèi)的類(lèi)型,而不是實(shí)例的類(lèi)型。 或者更確切的說(shuō),"告訴我 Greeter 標(biāo)識(shí)符的類(lèi)型",也就是構(gòu)造函數(shù)的類(lèi)型。 這個(gè)類(lèi)型包含了類(lèi)的所有靜態(tài)成員和構(gòu)造函數(shù)。 之后,就和前面一樣,我們?cè)?greeterMaker 上使用 new,創(chuàng)建 Greeter 的實(shí)例
把類(lèi)當(dāng)做接口使用
如上一節(jié)里所講的,類(lèi)定義會(huì)創(chuàng)建兩個(gè)東西:類(lèi)的實(shí)例類(lèi)型和一個(gè)構(gòu)造函數(shù)。 因?yàn)轭?lèi)可以創(chuàng)建出類(lèi)型,所以你能夠在允許使用接口的地方使用類(lèi)。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };