javascript語言基于函數和原型鍊繼承機制的方式建構可重用的元件。這對于oo方面程式設計來說顯得比較笨拙。在下一代的javascript标準ecmascript 6為我們提供了基于class base的oo設計方式。在typescript中我們也允許使用這種方式,typescript将編譯為目前大多數浏覽器能允許的普通javascript代碼,是以我們不用在等待ecmascript 6的到來了。
我們先看一個關于class-base的執行個體:
1
2
3
4
5
6
7
8
9
10
11
<code>class</code> <code>greeter {</code>
<code> </code><code>greeting: string;</code>
<code> </code><code>constructor(message: string) {</code>
<code> </code><code>this</code><code>.greeting = message;</code>
<code> </code><code>}</code>
<code> </code><code>greet() {</code>
<code> </code><code>return</code> <code>"hello, "</code> <code>+ </code><code>this</code><code>.greeting;</code>
<code>}</code>
<code>var</code> <code>greeter = </code><code>new</code> <code>greeter(</code><code>"world"</code><code>);</code>
這種文法和我們先前在c#,java語言看見的很相似。在這裡我們聲明了一個greeter的類,其中包含一個greeting的屬性,構造函數,以及greet的方法。
你也許已經注意到了例子中的‘this’關鍵字,’this‘和java/c#一樣代表對象執行個體的成員通路。
在最後一行我們利用‘new’關鍵字建立了一個greeter的對象執行個體。這将會建立一個對象執行個體,并調用我們先前定義的構造函數初始化此對象。
在typescript中我們可以使用我們常用的oo設計模式。當然對于oo設計最基本的是類型的繼承(繼承一個存在的類,複用存在的邏輯),下例就是一個關于類繼承的例子:
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<code>class</code> <code>animal {</code>
<code> </code><code>name:string;</code>
<code> </code><code>constructor(thename: string) { </code><code>this</code><code>.name = thename; }</code>
<code> </code><code>move(meters: number) {</code>
<code> </code><code>alert(</code><code>this</code><code>.name + </code><code>" moved "</code> <code>+ meters + </code><code>"m."</code><code>);</code>
<code>class</code> <code>snake </code><code>extends</code> <code>animal {</code>
<code> </code><code>constructor(name: string) { </code><code>super</code><code>(name); }</code>
<code> </code><code>move() {</code>
<code> </code><code>alert(</code><code>"slithering..."</code><code>);</code>
<code> </code><code>super</code><code>.move(5);</code>
<code>class</code> <code>horse </code><code>extends</code> <code>animal {</code>
<code> </code><code>alert(</code><code>"galloping..."</code><code>);</code>
<code> </code><code>super</code><code>.move(45);</code>
<code>var</code> <code>sam = </code><code>new</code> <code>snake(</code><code>"sammy the python"</code><code>);</code>
<code>var</code> <code>tom: animal = </code><code>new</code> <code>horse(</code><code>"tommy the palomino"</code><code>);</code>
<code>sam.move();</code>
<code>tom.move(34);</code>
在這個案例中展示了typescript的oo繼承方式,它和其他語言很相似。在typescript中我們采用‘extends’關鍵字來表示類的繼承關系。在這裡你可以看見 ‘horse’和’snake’都是繼承至’animal’的子類實作。
在案例中也展示如何去重寫父類的方法,在這裡’snake’和’horse都各自建立了一個‘move’方法來重寫父類’animal’的‘move’方法,并和‘super’關鍵字來調用父類的方法。
你可能注意到了在上例中我們并沒有用‘public’關鍵字去描述類的成員的通路級别讓其可見。在c#這類語言中,我們必須顯示的标注public關鍵字才能使得類的成員可見。但是在typescript中public為預設通路級别,而不是想c#一樣private預設。
有時我們希望封裝隐藏類的内部成員控制類成員的可見性,這個時候我們可以使用‘private’這類關鍵字來标示成員。如我們希望隐藏‘animal’的name屬性:
<code> </code><code>private</code> <code>name:string;</code>
typescript有一個結構化的類型(或者鴨子類型)系統。在我們比較兩個不同類型,我們不關心它們來自哪裡,隻關心對類型的每個成員的相容性。一旦所有的成員都是相容的,那麼我們就認為這兩個類型也是相容的。
當類型檢查系統比較兩個‘private’成員時,将會認為是不同的對象。對于兩個類型比較,當一個類型擁有私有成員的時候,那麼另外一個類必須包含相同聲明的私有變量(同一處聲明,多為繼承展現)。如下例:
<code> </code><code>private</code> <code>name:stringparameter properties;</code>
<code>class</code> <code>rhino </code><code>extends</code> <code>animal {</code>
<code> </code><code>constructor() { </code><code>super</code><code>(</code><code>"rhino"</code><code>); }</code>
<code>class</code> <code>employee {</code>
<code> </code><code>constructor(thename: string) { </code><code>this</code><code>.name = thename; } </code>
<code>var</code> <code>animal = </code><code>new</code> <code>animal(</code><code>"goat"</code><code>);</code>
<code>var</code> <code>rhino = </code><code>new</code> <code>rhino();</code>
<code>var</code> <code>employee = </code><code>new</code> <code>employee(</code><code>"bob"</code><code>);</code>
<code>animal = rhino;</code>
<code>animal = employee; </code><code>//error: animal and employee are not compatible</code>
在上例中我們有’animal’和‘rhino’兩個類型,’rhino’是‘animal’的一個子類。同時我們也定義了一個 ‘employee’的類,它和‘animal’類完全相同。我們分别建立了第三個類的對象,并互相指派,結果’animal’和’rhino’繼承關系,是以對于私有字段name在‘animal’中具有相同的聲明 ‘private name: string’,他們是相容的。但對于’employee’則各自聲明了一個私有name字段,對于私有字段是不相同的,是以我們不能将employee指派給animal對象,他們是不相容的類型。
通路限制關鍵字public’和’private也可以通過參數屬性方式快捷初始化類成員字段,參數屬性可以讓我們一步建立類成員。下例是上例中我們去掉了‘thename’,利用‘private name: string’聲明在構造函數參數上,它會為我們建立一個私有的name成員的同時初始化這個字段。
<code> </code><code>constructor(</code><code>private</code> <code>name: string) { }</code>
這裡我們利用‘private’關鍵字為類建立了一個私有成員并初始化其值。對于public也類似。
typescript支援利用getters/setters來控制對成員的通路。讓我們可以控制類的成員之間的通路方式。
下面示範如何轉化普通的類為get/set方式,如下是沒有get/set的方式:
<code> </code><code>fullname: string;</code>
<code>var</code> <code>employee = </code><code>new</code> <code>employee();</code>
<code>employee.fullname = </code><code>"bob smith"</code><code>;</code>
<code>if</code> <code>(employee.fullname) {</code>
<code> </code><code>alert(employee.fullname);</code>
在這裡我們允許任意的通路内部fullname成員。有時這可能不是我們所期望的。
在下邊我們希望将其轉化為在修改fullname的時候必須提供一個正确的passcode,使得不能任意修改此類name,如下:
<code>var</code> <code>passcode = </code><code>"secret passcode"</code><code>;</code>
<code> </code><code>private</code> <code>_fullname: string;</code>
<code> </code><code>get fullname(): string {</code>
<code> </code><code>return</code> <code>this</code><code>._fullname;</code>
<code> </code><code>set fullname(newname: string) {</code>
<code> </code><code>if</code> <code>(passcode && passcode == </code><code>"secret passcode"</code><code>) {</code>
<code> </code><code>this</code><code>._fullname = newname;</code>
<code> </code><code>}</code>
<code> </code><code>else</code> <code>{</code>
<code> </code><code>alert(</code><code>"error: unauthorized update of employee!"</code><code>);</code>
這裡我們在修改fullname屬性的時候驗證了passcode值,是否有權限修改。你可以嘗試修改passcode的值,使其不比對,觀察下會發生什麼問題?
注意:通路器使用我們需要設定編譯輸出為ecmascript 5。
回到類主題,上面我們所描述都是關于如何建立類的執行個體成員。我們同樣也可以建立類的靜态成員,其可見性為類級通路。我們可以使用’static’ 關鍵字标注類級成員。在下面的例子中表格原點對于所有表格都是通用的,是以我們可以用‘static’來定義類級成員。那麼可以采用類名(grid.)來通路通路該成員,類似于對象成員的’this.‘.
<code>class</code> <code>grid {</code>
<code> </code><code>static</code> <code>origin = {x: 0, y: 0};</code>
<code> </code><code>calculatedistancefromorigin(point: {x: number; y: number;}) {</code>
<code> </code><code>var</code> <code>xdist = (point.x - grid.origin.x);</code>
<code> </code><code>var</code> <code>ydist = (point.y - grid.origin.y);</code>
<code> </code><code>return</code> <code>math.sqrt(xdist * xdist + ydist * ydist) / </code><code>this</code><code>.scale;</code>
<code> </code><code>constructor (public scale: number) { }</code>
<code>var</code> <code>grid1 = </code><code>new</code> <code>grid(1.0); </code><code>// 1x scale</code>
<code>var</code> <code>grid2 = </code><code>new</code> <code>grid(5.0); </code><code>// 5x scale</code>
<code>alert(grid1.calculatedistancefromorigin({x: 10, y: 10}));</code>
<code>alert(grid2.calculatedistancefromorigin({x: 10, y: 10}));</code>
當我們在typescript中聲明一個類的時候,有時可能會建立多種聲明方式。首先類的執行個體方式:
<code>var</code> <code>greeter: greeter;</code>
<code>greeter = </code><code>new</code> <code>greeter(</code><code>"world"</code><code>);</code>
<code>alert(greeter.greet());</code>
這裡“var greeter: greeter”首先聲明一個greeter類的執行個體變量。這在很多oo語言中是很自然的方式。
同時也利用new關鍵字執行個體化了這個類的執行個體,并調用構造函數初始化該對象。下面我們可以看看同等的javascript将會如何去做:
<code>var</code> <code>greeter = (</code><code>function</code> <code>() {</code>
<code> </code><code>function</code> <code>greeter(message) {</code>
<code> </code><code>greeter.prototype.greet = </code><code>function</code> <code>() {</code>
<code> </code><code>};</code>
<code> </code><code>return</code> <code>greeter;</code>
<code>})();</code>
<code>var</code> <code>greeter;</code>
這裡’var greeter’被指派構造函數,并利用‘new’調用了這個方法得到類的執行個體。同樣我們的類也可以包含靜态變量。我們可以這麼認為所有的類都可以擁有執行個體和靜态兩種類型的成員。
讓我們對上例稍微做一些修改:
<code> </code><code>static</code> <code>standardgreeting = </code><code>"hello, there"</code><code>;</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>.greeting) {</code>
<code> </code><code>return</code> <code>"hello, "</code> <code>+ </code><code>this</code><code>.greeting;</code>
<code> </code><code>return</code> <code>greeter.standardgreeting;</code>
<code>var</code> <code>greeter1: greeter;</code>
<code>greeter1 = </code><code>new</code> <code>greeter();</code>
<code>alert(greeter1.greet());</code>
<code>var</code> <code>greetermaker: </code><code>typeof</code> <code>greeter = greeter;</code>
<code>greetermaker.standardgreeting = </code><code>"hey there!"</code><code>;</code>
<code>var</code> <code>greeter2:greeter = </code><code>new</code> <code>greetermaker();</code>
<code>alert(greeter2.greet());</code>
這裡‘greeter1’和上例工作很相似。我們初始化了‘greeter’類,并調用此對象。其結果在上例已經看見。
接着,我們直接使用了類通路。首先我們定義了一個新的‘greetermaker’的變量,這變量保持了greeter類的類型資訊,這裡我們使用的是‘typeof greeter’,這會傳回greeter自身的類類型資訊。這個類型資訊中會包含是以的靜态成員資訊和執行個體化對象的構造函數資訊。然後通過‘new’ greetermaker來建立一個greeter的執行個體對象,在調用其方法greet。
如上所述,類主要聲明了類執行個體類型和構造函數兩件事。因為類主要建立類型,是以我們可以在同一地方使用interface來替代它:
<code>class</code> <code>point {</code>
<code>x: number;</code>
<code>y: number;</code>
<code>interface</code> <code>point3d </code><code>extends</code> <code>point {</code>
<code>z: number;</code>
<code>var</code> <code>point3d: point3d = {x: 1, y: 2, z: 3};</code>
注意:typescript更準确說是為了類型檢查的類型推斷。