TypeScript--類(class)
ES6 中的類
傳統的 JavaScript 程式使用函式和基於原型的繼承來建立可重用的元件,但對於熟悉使用面向物件方式的程式設計師來講就有些棘手,因為他們用的是基於類的繼承並且物件是由類構建出來的。 從ECMAScript 2015,也就是 ECMAScript 6開始,JavaScript 程式設計師將能夠使用基於類的面向物件的方式。 使用 TypeScript,我們允許開發者現在就使用這些特性,並且編譯後的 JavaScript 可以在所有主流瀏覽器和平臺上執行,而不需要等到下個 JavaScript 版本。
我們在 es6 中是這樣寫類的,新建個 index.js 檔案
class Point{ //建立了一個類,類名為 Point constructor (x,y) { // 構造方法,相當於 python 類中的 __init__ 方法 this.x = x; this.y = y; } getpoint(){ return `(${this.x},${this.y})` } } const p1 = new Point(1,2) // 例項化類 console.log(p1) console.log(p1.getpoint()) // 呼叫 getpoint 方法
TypeSctipt 中的類
class Greeter { //宣告一個Greeter類 greeting: string; // greeting的屬性 constructor(message: string) { // 建構函式 this.greeting = message; } greet() { // greet方法 return "Hello, " + this.greeting; } } // 使用 new 構造了 Greeter 類的一個例項。 它會呼叫之前定義的建構函式,建立一個 Greeter 型別的新物件,並執行建構函式初始化它。 let greeter = new Greeter("world");
我們宣告一個 Greeter 類。這個類有3個成員:一個叫做 greeting 的屬性,一個建構函式和一個 greet 方法。
你會注意到,我們在引用任何一個類成員的時候都用了 this。 它表示我們訪問的是類的成員。
最後一行,我們使用 new 構造了 Greeter 類的一個例項。 它會呼叫之前定義的建構函式,建立一個 Greeter 型別的新物件,並執行建構函式初始化它。
繼承
在 TypeScript 裡,我們可以使用常用的面向物件模式。 基於類的程式設計中一種最基本的模式是允許使用繼承來擴充套件現有的類。
class Animal { move(distanceInMeters: number = 0) { console.log(`Animal moved ${distanceInMeters}m.`); } } class Dog extends Animal { // Dog 類繼承了 Animal 類 bark() { console.log('Woof! Woof!'); } } const dog = new Dog(); dog.bark(); dog.move(10); dog.bark();
類從基類中繼承了屬性和方法。 這裡,Dog 是一個派生類,它派生自 Animal 基類,通過 extends 關鍵字。 派生類通常被稱作子類,基類通常被稱作超類。
因為 Dog 繼承了 Animal 的功能,因此我們可以建立一個 Dog 的例項,它能夠 bark() 和 move()。
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); // 必須呼叫super() } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); // 必須呼叫super() } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
這一次,我們使用 extends 關鍵字建立了 Animal 的兩個子類:Horse 和 Snake。
與前一個例子的不同點是,派生類包含了一個建構函式,它必須呼叫 super(),它會執行基類的建構函式。 而且,在建構函式裡訪問 this 的屬性之前,我們一定要呼叫 super()。 這個是 TypeScript 強制執行的一條重要規則。
這個例子演示瞭如何在子類裡可以重寫父類的方法。 Snake 類和 Horse 類都建立了 move 方法,它們重寫了從 Animal 繼承來的 move 方法,使得 move 方法根據不同的類而具有不同的功能。 注意,即使 tom 被宣告為 Animal 型別,但因為它的值是 Horse,呼叫 tom.move(34) 時,它會呼叫 Horse 裡重寫的方法
公共,私有與受保護的修飾符
預設為public
在上面的例子裡,我們可以自由的訪問程式裡定義的成員。 如果你對其它語言中的類比較瞭解,就會注意到我們在之前的程式碼裡並沒有使用
public
來做修飾;例如,C# 要求必須明確地使用public
指定成員是可見的。 在 TypeScript 裡,成員都預設為public
。
你也可以明確的將一個成員標記成public
。 我們可以用下面的方式來重寫上面的Animal
類:
class Animal { public name: string; public constructor(theName: string) { this.name = theName; } public move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
private
當成員被標記成private
時,它就不能在宣告它的類的外部訪問。比如:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // 錯誤: 'name' 是私有的.
protected
protected
修飾符與private
修飾符的行為很相似,但有一點不同,protected
成員在派生類中仍然可以訪問。例如:
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); // 錯誤
我們不能在Person
類外使用name
,但是我們仍然可以通過Employee
類的例項方法訪問,因為Employee
是由Person
派生而來的。
建構函式也可以被標記成protected
。 這意味著這個類不能在包含它的類外被例項化,但是能被繼承。比如
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"); // 錯誤: 'Person' 的建構函式是被保護的.
readonly修飾符
你可以使用readonly
關鍵字將屬性設定為只讀的。 只讀屬性必須在宣告時或建構函式裡被初始化
class Octopus { readonly name: string; // readonly readonly numberOfLegs: number = 8; // readonly constructor (theName: string) { this.name = theName; } } let dad = new Octopus("Man with the 8 strong legs"); dad.name = "Man with the 3-piece suit"; // 錯誤! name 是隻讀的.
引數屬性
在上面的例子中,我們不得不定義一個受保護的成員name
和一個建構函式引數theName
在Person
類裡,並且立刻給name
和theName
賦值。 這種情況經常會遇到。引數屬性可以方便地讓我們在一個地方定義並初始化一個成員。 下面的例子是對之前Animal
類的修改版,使用了引數屬性:
class Animal { constructor(private name: string) { } move(distanceInMeters: number) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
注意看我們是如何捨棄了theName
,僅在建構函式裡使用private name: string
引數來建立和初始化name
成員。 我們把宣告和賦值合併至一處。
引數屬性通過給建構函式引數新增一個訪問限定符來宣告。 使用private
限定一個引數屬性會宣告並初始化一個私有成員;對於public
和protected
來說也是一樣。
靜態屬性
到目前為止,我們只討論了類的例項成員,那些僅當類被例項化的時候才會被初始化的屬性。 我們也可以建立類的靜態成員,這些屬性存在於類本身上面而不是類的例項上。 在這個例子裡,我們使用static
定義origin
,因為它是所有網格都會用到的屬性。 每個例項想要訪問這個屬性的時候,都要在origin
前面加上類名。 如同在例項屬性上使用this.
字首來訪問屬性一樣,這裡我們使用Grid.
來訪問靜態屬性。
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}));
抽象類
抽象類做為其它派生類的基類使用。 它們一般不會直接被例項化。 不同於介面,抽象類可以包含成員的實現細節。abstract
關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
abstract class Animal { abstract makeSound(): void; move(): void { console.log('roaming the earch...'); } }
抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。 抽象方法的語法與介面方法相似。 兩者都是定義方法簽名但不包含方法體。 然而,抽象方法必須包含abstract
關鍵字並且可以包含訪問修飾符。
abstract class Department { constructor(public name: string) { } printName(): void { console.log('Department name: ' + this.name); } abstract printMeeting(): void; // 必須在派生類中實現 } class AccountingDepartment extends Department { constructor() { super('Accounting and Auditing'); // 在派生類的建構函式中必須呼叫 super() } printMeeting(): void { console.log('The Accounting Department meets each Monday at 10am.'); } generateReports(): void { console.log('Generating accounting reports...'); } } let department: Department; // 允許建立一個對抽象型別的引用 department = new Department(); // 錯誤: 不能建立一個抽象類的例項 department = new AccountingDepartment(); // 允許對一個抽象子類進行例項化和賦值 department.printName(); department.printMeeting(); department.generateReports(); // 錯誤: 方法在宣告的抽象類中不存在