傳統的JavaScript語言基於函數和原型鏈繼承機制的方式構建可重用的組件,但這對於OO編程人員來講顯得比較笨拙,由於是在類的基礎上來繼承。從JavaScript標準ECMAScript 6開始能夠採用面向對象基於類來構建應用。在TypeScript中開發人員如今就可使用這些技術,TypeScript能夠將它們編譯爲目前大多數瀏覽器和平臺能容許的普通Javascript代碼,能夠不用等待下一版本的JavaScript的到來。java
咱們先看一個基於類的簡單例子:
typescript
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.',表示這是一個成員訪問。
c#
在最後一行咱們利用‘new’關鍵字建立了一個'Greeter'類的實例,這會用'Greeter' shape新建一個對象,並調用咱們先前定義的構造函數來初始化此對象。
設計模式
在TypeScript中咱們可使用咱們經常使用的OO設計模式。固然在基於類的編程中,最基本的一個模式是能夠經過繼承來擴展存在的類,建立出新的類。可看下面的例子:瀏覽器
class Animal { name:string; constructor(theName: string) { this.name = theName; } move(meters: number = 0) { alert(this.name + " moved " + meters + "m."); } } class Snake extends Animal { constructor(name: string) { super(name); } move(meters = 5) { alert("Slithering..."); super.move(meters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(meters = 45) { alert("Galloping..."); super.move(meters); } } var sam = new Snake("Sammy the Python"); var tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
這個例子展現的TypeScript中一些繼承特性在其餘語言中也能夠看到。這裏看到用 'extends' 關鍵字來建立一個子類。'Horse'和'Snake'子類都繼承自基類'Animal', 能夠訪問'Animal'的特性。 ide
這個例子也展現了子類中的方法可重載(override)基類中的方法,’Snake’和’Horse'子類都各自建立了一個‘move’方法來重載基類’Animal’的‘move’方法,這樣每一個子類就能夠實現特定的功能。函數
你可能注意到了在上例中咱們並無用'public'去描述類的每個成員使其可見。在相似於C#語言中,必須顯式地標註'public'關鍵字才能使得類的成員可見。可是在TypeScript中。每一個成員缺省就是public。this
有時咱們但願控制類成員不能被外部看到,就能夠將這些成員標記爲private。下面代碼中咱們但願隱藏上一章節中'Animal'類的name屬性:
編碼
class Animal { private name:string; constructor(theName: string) { this.name = theName; } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
TypeScript是一個結構化的類型系統。當比較兩個不一樣類型,不關心它們來自哪裏,若是每一個成員的類型都是兼容的,那麼就認爲這兩個類型也是兼容的。
當比較有'private'成員的類型時,就須要另外處理。當比較兩個類型時,若是一個類型擁有私有成員,那麼另一個類型必須包含源於同一個聲明的私有成員,才認爲這兩個類型是兼容的。
可參見下面例子來講明:
class Animal { private name:string; 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'類從形狀(shape)上看徹底相同。咱們建立了這三個類的實例,並相互賦值看看會發生什麼。由於'Animal'和'Rhino'共享'Animal'中相同的私有訪問聲明'private name: string',所以它們是兼容的。可是當咱們將'Employee'賦值給'Animal'時,獲得類型不兼容錯誤。雖然'Employee'也有一個私有成員'name',但它與 'Animal'中的私有成員'name'是不相同的,所以它們是不兼容的類型。
能夠經過關鍵字public’和’private建立快捷參數屬性方式,來建立並初始化類成員字段。參數屬性可讓咱們僅用一步就能夠建立和初始化類成員。下例是上例中咱們去掉了‘theName’,在構造函數中使用‘private name: string’參數,來建立'name'成員的同時初始化這個字段。
class Animal { constructor(private name: string) { } move(meters: number) { alert(this.name + " moved " + meters + "m."); } }
這裏利用'private'爲參數屬性類建立了一個私有成員並初始化其值,對於public也相似。
TypeScript支持利用getters/setters來控制對成員的訪問,這樣能夠更細粒度來控制類的成員訪問方式。
下面將一個類轉化爲使用'get'和'set'方式。先從沒有getters/setters的例子開始:
class Employee { fullName: string; } var employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
雖然直接設置'fullName'成員很方便,但若是有人隨意改變人名可能會形成麻煩。
在下邊,咱們但願將其轉化爲必須提供一個secret passcode,才能修改employee。經過'set'關鍵字來代替直接訪問fullName成員,相應地增長一個'get'關鍵字來訪問fullName成員:
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); }
爲了證實如今的訪問器(Accessors)驗證了passcode值,能夠嘗試修改passcode的值,使其不匹配,就會獲得沒有權限更新employee的告警信息。
注意:訪問器須要設置編譯輸出爲ECMAScript 5。
到這裏,咱們只是討論了實例化類的成員,當實例化時就能夠經過對象來訪問成員。咱們也能夠建立類的靜態成員,是經過類來訪問而不是經過實例化對象來訪問。在下面這個例子中,咱們對原點('origin')成員使用’static’ 關鍵字,由於origin是全部grid的一個通用值。每一個實例經過類名爲前綴來訪問這個值。相似於在實例訪問成員前面用'this.',對靜態訪問成員前面用類名Grid。
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做爲類Greeter實例的類型。這對於其餘面嚮對象語言的編程人員來講是很天然的方式。
還建立了一個構造函數,這個函數是在用'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類的類型,而不是實例類型。或者更準確地說,給出符號Greeter的類型就是構造函數的類型。這個類型包含Greeter全部的靜態成員,以及建立Greeter類實例的構造函數。咱們能夠用'new greeterMaker'來建立'Greeter'的實例,而後調用其方法。
如上所述,類聲明建立了兩個東西:一個是類實例的類型,一個是構造函數。由於類建立了類型,因此咱們能夠將類用在使用接口的地方。
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } var point3d: Point3d = {x: 1, y: 2, z: 3};
[1] http://www.typescriptlang.org/Handbook#classes
[2] TypeScript - Classes, 破狼blog, http://greengerong.com/blog/2014/11/17/typescript-classes/
[3] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012
[4] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181
[5] TypeScript手冊翻譯系列2-接口, http://my.oschina.net/1pei/blog/493388