簡介
JavaScript語言基於函數和原型鏈繼承機制的方式構建可重用的組件。這對於OO方面編程來說顯得比較笨拙。在下一代的JavaScript標准ECMAScript 6為我們提供了基於class base的OO設計方式。在TypeScript中我們也允許使用這種方式,TypeScript將編譯為目前大多數瀏覽器能允許的普通Javascript代碼,所以我們不用在等待ECMAScript 6的到來了。
類
我們先看一個關於class-base的實例:
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } var greeter = new Greeter("world");
這種語法和我們先前在c#,java語言看見的很相似。在這里我們聲明了一個Greeter的類,其中包含一個greeting的屬性,構造函數,以及greet的方法。
你也許已經注意到了例子中的‘this’關鍵字,’this‘和java/C#一樣代表對象實例的成員訪問。
在最后一行我們利用‘new’關鍵字創建了一個Greeter的對象實例。這將會新建一個對象實例,並調用我們先前定義的構造函數初始化此對象。
繼承
在TypeScript中我們可以使用我們常用的OO設計模式。當然對於OO設計最基本的是類型的繼承(繼承一個存在的類,復用存在的邏輯),下例就是一個關於類繼承的例子:
class Animal { name:string; constructor(theName: string) { this.name = theName; } move(meters: number) { alert(this.name + " moved " + meters + "m."); } } class Snake extends Animal { constructor(name: string) { super(name); } move() { alert("Slithering..."); super.move(5); } } class Horse extends Animal { constructor(name: string) { super(name); } move() { alert("Galloping..."); super.move(45); } } var sam = new Snake("Sammy the Python"); var tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
在這個案例中展示了TypeScript的OO繼承方式,它和其他語言很相似。在TypeScript中我們采用‘extends’關鍵字來表示類的繼承關系。在這里你可以看見 ‘Horse’和’Snake’都是繼承至’Animal’的子類實現。
在案例中也展示如何去重寫父類的方法,在這里’Snake’和’Horse都各自創建了一個‘move’方法來重寫父類’Animal’的‘move’方法,並和‘super’關鍵字來調用父類的方法。
Private/Public訪問限制
Public為默認行為
你可能注意到了在上例中我們並沒有用‘public’關鍵字去描述類的成員的訪問級別讓其可見。在C#這類語言中,我們必須顯示的標注public關鍵字才能使得類的成員可見。但是在TypeScript中public為默認訪問級別,而不是想c#一樣private默認。
有時我們希望封裝隱藏類的內部成員控制類成員的可見性,這個時候我們可以使用‘private’這類關鍵字來標示成員。如我們希望隱藏‘Animal’的name屬性:
class Animal { private name:string; constructor(theName: string) { this.name = theName; } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
理解private(私有)
TypeScript有一個結構化的類型(或者鴨子類型)系統。在我們比較兩個不同類型,我們不關心它們來自哪里,只關心對類型的每個成員的兼容性。一旦所有的成員都是兼容的,那么我們就認為這兩個類型也是兼容的。
當類型檢查系統比較兩個‘private’成員時,將會認為是不同的對象。對於兩個類型比較,當一個類型擁有私有成員的時候,那么另外一個類必須包含相同聲明的私有變量(同一處聲明,多為繼承體現)。如下例:
class Animal { private name:stringParameter properties; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name:string; constructor(theName: string) { this.name = theName; } } var animal = new Animal("Goat"); var rhino = new Rhino(); var employee = new Employee("Bob"); animal = rhino; animal = employee; //error: Animal and Employee are not compatible
在上例中我們有’Animal’和‘Rhino’兩個類型,’Rhino’是‘Animal’的一個子類。同時我們也定義了一個 ‘Employee’的類,它和‘Animal’類完全相同。我們分別創建了第三個類的對象,並相互賦值,結果’Animal’和’Rhino’繼承關系,所以對於私有字段name在‘Animal’中具有相同的聲明 ‘private name: string’,他們是兼容的。但對於’Employee’則各自聲明了一個私有name字段,對於私有字段是不相同的,所以我們不能將employee賦值給animal對象,他們是不兼容的類型。
參數屬性(Parameter properties)
訪問限制關鍵字public’和’private也可以通過參數屬性方式快捷初始化類成員字段,參數屬性可以讓我們一步創建類成員。下例是上例中我們去掉了‘theName’,利用‘private name: string’聲明在構造函數參數上,它會為我們創建一個私有的name成員的同時初始化這個字段。
class Animal { constructor(private name: string) { } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
這里我們利用‘private’關鍵字為類創建了一個私有成員並初始化其值。對於public也類似。
訪問器(Accessors)
TypeScript支持利用getters/setters來控制對成員的訪問。讓我們可以控制類的成員之間的訪問方式。
下面演示如何轉化普通的類為get/set方式,如下是沒有get/set的方式:
class Employee { fullName: string; } var employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
在這里我們允許任意的訪問內部fullName成員。有時這可能不是我們所期望的。
在下邊我們希望將其轉化為在修改fullName的時候必須提供一個正確的passcode,使得不能任意修改此類name,如下:
var 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 { alert("Error: Unauthorized update of employee!"); } } } var employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
這里我們在修改fullName屬性的時候驗證了passcode值,是否有權限修改。你可以嘗試修改passcode的值,使其不匹配,觀察下會發生什么問題?
注意:訪問器使用我們需要設置編譯輸出為ECMAScript 5。
靜態屬性
回到類主題,上面我們所描述都是關於如何創建類的實例成員。我們同樣也可以創建類的靜態成員,其可見性為類級訪問。我們可以使用’static’ 關鍵字標注類級成員。在下面的例子中表格原點對於所有表格都是通用的,所以我們可以用‘static’來定義類級成員。那么可以采用類名(Grid.)來訪問訪問該成員,類似於對象成員的’this.‘.
class Grid { static origin = {x: 0, y: 0}; calculateDistanceFromOrigin(point: {x: number; y: number;}) { var xDist = (point.x - Grid.origin.x); var yDist = (point.y - Grid.origin.y); return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; } constructor (public scale: number) { } } var grid1 = new Grid(1.0); // 1x scale var grid2 = new Grid(5.0); // 5x scale alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
高級特性
構造函數
當我們在TypeScript中聲明一個類的時候,有時可能會創建多種聲明方式。首先類的實例方式:
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } var greeter: Greeter; greeter = new Greeter("world"); alert(greeter.greet());
這里“var greeter: Greeter”首先聲明一個Greeter類的實例變量。這在很多OO語言中是很自然的方式。
同時也利用new關鍵字實例化了這個類的實例,並調用構造函數初始化該對象。下面我們可以看看同等的JavaScript將會如何去做:
var Greeter = (function () { function Greeter(message) { this.greeting = message; } Greeter.prototype.greet = function () { return "Hello, " + this.greeting; }; return Greeter; })(); var greeter; greeter = new Greeter("world"); alert(greeter.greet());
這里’var Greeter’被賦值構造函數,並利用‘new’調用了這個方法得到類的實例。同樣我們的類也可以包含靜態變量。我們可以這么認為所有的類都可以擁有實例和靜態兩種類型的成員。
讓我們對上例稍微做一些修改:
class Greeter { static standardGreeting = "Hello, there"; greeting: string; greet() { if (this.greeting) { return "Hello, " + this.greeting; } else { return Greeter.standardGreeting; } } } var greeter1: Greeter; greeter1 = new Greeter(); alert(greeter1.greet()); var greeterMaker: typeof Greeter = Greeter; greeterMaker.standardGreeting = "Hey there!"; var greeter2:Greeter = new greeterMaker(); alert(greeter2.greet());
這里‘greeter1’和上例工作很相似。我們初始化了‘Greeter’類,並調用此對象。其結果在上例已經看見。
接着,我們直接使用了類訪問。首先我們定義了一個新的‘greeterMaker’的變量,這變量保持了Greeter類的類型信息,這里我們使用的是‘typeof Greeter’,這會返回Greeter自身的類類型信息。這個類型信息中會包含所以的靜態成員信息和實例化對象的構造函數信息。然后通過‘new’ greeterMaker來創建一個Greeter的實例對象,在調用其方法greet。
利用interface來使用class
如上所述,類主要聲明了類實例類型和構造函數兩件事。因為類主要創建類型,所以我們可以在同一地方使用interface來替代它:
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } var point3d: Point3d = {x: 1, y: 2, z: 3};
注意:TypeScript更准確說是為了類型檢查的類型推斷。