孫廣東 2016.4.5php
JavaScript現在處處都是。web、server(經過NodeJS)、移動應用(經過各類框架)。所有這些,TypeScript都可以使用,並且可以爲JavaScript擴展出面向對象和靜態類型的特徵。css
TypeScript能讓咱們提早使用將來的語言特性。甚至不少其它,好比泛型這樣的語言特性。vue
TypeScript代碼。終於會編譯爲地道的JavaScript,兼容一切使用JavaScript的場合。ios
編譯過程主要是編譯時檢查,一點改寫,刪除類型批註和接口。刪除類型批註和接口這個過程稱爲類型擦除(Type Erasure)。c++
我從網上找到一張很是好的圖片用來講明類型擦除,例如如下。git
咱們後面將具體介紹TypeScript並對照他和其它語言的異同,主要是C#。github
這個話題很是難說清楚,但是很是有必要在提到TypeScript的時候講一下。這兩個詞:編譯Compiling,Transpiling有人譯做轉譯。這是一個英文計算機術語。web
通常以爲轉譯是一種特殊的編譯,當將一種源碼語言編譯成第二種源碼語言時。就稱爲轉譯。sql
當編譯一個c#程序時,是由源碼語言C#編譯爲IL。這就不能稱爲Transpiling。因爲他們是全然不一樣的東西。typescript
而編譯TypeScript程序時,他變成了第二種源碼JavaScript,這個就稱爲Transpiling(轉譯)。
但無論怎樣,Transpiling是Compiling的特例。Transpiling也屬於Compiling。
因此TypeScript轉譯爲Javascript。TypeScript編譯爲JavaScript,都是沒有問題的講法。
這裏高速介紹一下TypeScript的關鍵語法,比方顯示類型批註、類、接口。
儘管C# Java程序猿都很是熟悉面向對象。但TypeScript並不基於C#,因此仍是有所差異的。
TypeScript是靜態類型語言,需要編譯。擁有編譯時類型檢查的特性。
編譯時類型檢查可以確保類型安全。並方便開發更智能的本身主動完畢功能。實際上TypeScript的各類開發工具都作得很是不錯。
比方VisualStudio,編寫TypeScript文件時,就比編寫JavaScript要聰明的多。
這就是靜態類型帶來的優勢。
TypeScript文件的擴展名爲」.ts」,你可以使用很是多工具編寫.ts文件,比方visualstudio。不少其它信息。請看官網http://www.typescriptlang.org/
官網還提供了一個在線編寫測試.ts文件的環境http://www.typescriptlang.org/Playground/
一個TypeScript應用包括多個TypeScript代碼文件。一個代碼文件可以包括多個Class,Class也可以組成模塊。
模塊的概念和C#中組織類型的namespace比較接近。
運行時。TypeScript編譯獲得的JavaScript可以經過Html標籤<Script>加入網頁中,也可以使用其它的模塊載入工具,比方NodeJS就內置了模塊載入工具。
不使用模塊組織依賴的時候,一個TypeScript文件依賴還有一個TypeScript文件,應該加上引用凝視
(這是可選的。通常也不使用命令行編譯。大部分圖形化工具不加也可以)。
當使用模塊載入工具的時候(比方RequireJS。或者NodeJS內置載入工具),代碼例如如下:
不知道啥時RequireJS的同窗請自行補課。
當你使用import語句的時候,是有兩種模式的,CommonJS 和 AMD模式,他們編譯爲JavaScript生成的代碼不一樣,這個依據你使用不一樣的載入工具請本身設置TypeScript編譯選項。
注:RequireJS使用CommonJS模式,NodeJS使用AMD模式。
TypeScript的基本類型有 string,number,boolean,null和undefined.
因爲JavaScript沒有整數小數這些區分,因此TypeScript也沒有加入,統一使用number。
另外TypeScript加入了一個any類型,當需要動態類型時使用。
這個與C#的動態keyword是類似的。
TypeScript支持數組類型,經過在類型後面加入方括號定義。比方string[].
也可以本身定義類型。在講到class的時候咱們細說。
當右側表達式足以肯定變量的類型時。TypeScript可以本身主動肯定變量的類型。這個特性C#也有。
比方這個代碼
假設在VisualStudio中,鼠標懸停,依舊可以看到這三個變量被識別出來是各自不一樣的類型。
類型判斷還能處理更復雜的類型信息,比方這個代碼,無需類型標註就能夠得到靜態類型的優勢。
寫完這個代碼之後,以後再鍵入example,再敲入.,也可以得到本身主動完畢提示。.name,.id.collection.
類型判斷始終是有侷限性的,基本上是依靠右側表達式來判斷。
假設聲明變量時沒有判斷出來,過後賦值也判斷不出來了。
比方如下的代碼。
當聲明變量時沒法判斷出,變量即被標註爲 any 類型,any類型全然沒有不論什麼類型安全方面的保證。固然本身主動完畢功能也沒有。
TypeScript的類型是設計爲批註,而不是定義。是可選的。因此和C#的類型寫在前面不一樣,類型是以可選的形式寫在後面的。
咱們將上面的所有代碼都加上類型標註
這些標註的加入和上面本身主動類型判斷的結果是同樣的。但是閱讀代碼的人就可以一眼看出。
模塊、類型、接口
TypeScript的模塊用於代碼組織。類似C#的namespace。
一個模塊可以包括多個類和接口。可以將類和接口私有化或者導出,導出的意思就是公開,讓其它模塊可以訪問他們。
TypeScript的class和C#的class意義一樣。實際上TypeScript的一個亮點就是他隱藏了JavaScript的原型設計,而是採用了更流行的基於類型的面向對象方式。
你可以擴展其它的class,實現多個接口,加入構造函數。公開屬性和方法。這些都和c#的class很是類似。
屬性和方法可以使用public 或private訪問修飾符修飾。當在構造函數的參數上使用訪問修飾符的時候,他們會本身主動爲該類型加入同名的屬性。
請很是當心這個語法。這和很是多語言都不一樣
如圖中Point的構造函數參數x。y 使用了public 訪問修飾符,因此會本身主動生成Point的成員變量x和y。這是TypeScript的特有語法。
圖中的export keyword使類和接口在模塊外部可見。實現接口使用implementskeyword,繼承類使用extendskeyword。這點和C#直接用一個冒號表達不一樣。
當你擴展一個類時。用superkeyword調用基類的方法。用thiskeyword來調用當前類的屬性和方法。重寫基類的方法是可選的。構造函數必需要調用基類的構造函數,編譯器會提醒你的。
模塊名稱包括點,一個模塊可以定義在多個文件裏。
模塊的名字別太長,訪問的時候打字會比較累。
下圖是一個比較累的樣例
TypeScript的函數參數擁有豐富的特性:可選參數、默認參數、不定參數。
有一些和其它語言的設計不太一樣。如下將一一說明。
儘管在設計上,這些參數特性都不是必要的。但是TypeScript的這些地方都有些特殊性。你得了解一下。以備看懂。
可選參數的設計和C#基本一致,符號用? 表示可選參數。可選參數必須出現在必選參數以後。
默認參數僅僅要在定義時給附上值就能夠了,並且和c#不一樣,TypeScript的默認參數不需要是常量,運行時可解釋就能夠,後文會有說明
下圖是特殊的默認參數
不定參數可以指定隨意數量,可以爲零。這個設計也和c#類似,僅僅是要加入三個點。
如下就是不定參數的一個典型使用場景
Javascript是不一樣意重名函數的,TypeScript實現的重載和c#有很是大的不一樣。
TypeScript的重載是要把所有的函數簽名寫出來,寫一份實現,並且最後一個函數簽名要能包括上面所有的函數簽名。
例如如下圖:
和C#三個重載的函數就擁有三個函數體不一樣,TypeScript的重載事實上全是 overloadedMethod(input:any)這最後一個簽名的實現。上面的兩個僅僅是兩個兼容的簽名。但是配合可選參數仍是可以表現出和c#的函數重載類似的調用方式。
函數編寫方式免不了要寫很是多的if了。
TypeScript的枚舉很是類似C#,你可以指定值。
也可以反過來經過枚舉值取到枚舉的名字
這裏面的細節就再也不贅述了。你可以觀察枚舉編譯爲JavaScript之後是什麼樣子
對c#程序猿來講,TypeScript的泛型很是熟悉,基本上是一致的設計。
C#使用wherekeyword標記類型約束,TypeScript在尖括號內使用extendskeyword,效果一樣。
如下的樣例中IExample約束了泛型必須是IMyInterface和他的派生類。
假設像下圖這樣用的話,就能約束爲同一時候繼承ClassOne和ClassTwo的類型。很是費解吧。請特別注意。
這是因爲本質上TypeScript的類型系統並不那麼嚴格。如下的章節會詳解TypeScript的類型系統
你也可以使用泛型做爲泛型的類型約束。例如如下
C#的類型系統是強制標記的,對象的類型必須顯示聲明。即便兩個類型擁有全然一樣的結構,他們也不是一樣的類型。
TypeScript的類型系統是結構上的,建築結構。層次型的。
結構一樣的類型就能夠以爲是同一類型。
如下是C#中的一個樣例
ClassA和 ClassB是全然不一樣的類型,他們之間是不一樣的,必須顯示繼承接口才幹讓他們兼容。
而在TypeScript中不是這樣,咱們用例如如下的樣例來證實。
ClassA ClassB ExampleC 擁有簽名一致的函數。因此他們就可以兼容。
TypeScript的結構類型系統意味着你在c#中的觀念再也不成立,classname不是關鍵。
這需要咱們寫代碼的時候時刻注意。
這玩意會讓代碼變幻無窮。假設你熟悉C#或者JAVA。這可能會讓你困惑。
看如下的樣例,不需要classkeyword,也會實實在在的產生類型。
在這個樣例中,會產生一個匿名的類型
這個匿名的類型可以讓開發工具提供本身主動完畢功能,編譯器也會檢查。假設你嘗試將objA的name賦值一個數值,編譯器會檢查到告訴你錯誤。
編譯器還會爲數組判斷類型。
TypeScript的訪問修飾器可能會給你一種弱小的感受,的確如此。他僅僅是一個編譯時功能。
模塊中的一切均爲私有,除非加上exportkeyword。沒有exportkeyword的類型僅能在模塊內實用。
類的內部,一切均爲公開,除非加上private keyword。public僅僅是爲了看起來意圖明白。
TypeScript的訪問修飾器就是這樣而已,沒有c#的 internal 和 protected這樣的修飾器。想要c#類似的功能就放棄吧。
內存管理
當你運行TypeScript 程序時,他會被編譯爲JavaScript程序來運行。
JavaScript的內存管理和C#比較接近。
內存在對象建立時分配,在對象再也不使用時回收。不一樣的是垃圾回收機制在JavaScript的世界裏沒有標準統一的實現,這意味着你的JavaScript程序的內存性能相比C#難以預測。
比方說。在比較早的瀏覽器上。可能使用的是引用計數垃圾回收機制。當一個對象的引用計數達到0時。將回收內存。這樣的垃圾回收機制比較高速和即時。但是當發生循環引用時,引用計數將永遠也沒法達到零。
比較新的瀏覽器使用標記與清掃垃圾回收機制來找出不可訪問到的對象,很是大程度上避免了這個問題。這樣的垃圾回收機制比較緩慢。但他能避免循環引用致使的內存泄露。
關於兩種垃圾回收機制有很是多的資料可以研究。這裏僅僅是想告訴你,別相信你的直覺,瀏覽器會很是不一樣。
在TypeScript中,一般都不會使用到非託管的資源。Node.JS中多一點。大部分瀏覽器將底層交互API設計爲回調控制,不向你暴露對象。不需要你本身管理非託管資源。比方如下接近傳感器的API用法。
如你所見,使用回調並不需要管理不論什麼引用。Node.js中有時會碰到需要本身釋放的對象。你要保證釋放這些對象,不然就會內存泄露。你可以使用 try finally 塊。釋放代碼寫在 finally 塊中。這樣就能保證就算髮生不論什麼錯誤。釋放代碼都會運行。
在TypeScript中,你可以用throwkeyword引起一個異常。
在JavaScript中,throw可以throw不論什麼類型的東西。但是再TypeScript中,throw的必須是一個Error對象。
要本身定義異常,可以繼承Error類。當你需要一個特定的異常行爲或者你但願catch塊可以分辨異常類型時,本身定義異常就會很是實用。
處理異常需要使用try catch語句塊。大致上和c#的用法是很是接近的,但是c#支持多個catch塊,TpyeScript不可以。你可以用error.name來區分異常類型。
假設需要一些即便發生異常也會調用的代碼,你就需要finally語句塊了,他們的運行順序例如如下圖
TypeScript 從0.9開始,數組就是可以指定內容的準確類型。
用法是,用類型批註加上方括號。TypeScript會檢查加入到數組中的條目的類型,也會判斷從數組中檢索的條目的類型。
因爲TypeScript沒有本身的框架庫,因此僅僅能使用內置的JavaScript函數和瀏覽器接口,沒有像C#的List<T> 這樣的泛型庫。
但這並不能阻止咱們本身創造一個。
如下是一個泛型列表類的樣例,僅僅是個演示。
上面這個List類演示了不少TypeScript的語言特性和用法。就像C#的List<T>那樣。
如下是怎樣使用這個List類的樣例。
TypeScript中的Date對象是基於JavaScriptDate 對象的,他是由從1970年零點 utc時間開始的毫秒數。
Date對象可以接受各類精度的初始化,例如如下。
你也可以使用RFC和ISO格式的字符串初始化Date對象。固然,毫秒數也可以(從1970年1月1日開始)。
請注意。這不是時間戳。時間戳單位是秒,數字比這個少幾個零。
你可以訪問現在的日期與時間,經過方法Date.Now();返回值是毫秒。你假設需要用Date對象去操做。需要用這個值去初始化一個Date對象
當你有一個Date對象時,你可以用內部方法獲取日期的一部分。有兩套方法,一套本地,一套UTC。在如下的實例中。你看到咱們把getUTCMonth的值加了1.因爲返回值從零開始。因此一月是 0。2 月是 1。等等。
日期的各個部分爲年、月、日、小時、分鐘、秒、毫秒。所有這些值都可以在獲取本地的和 UTC 的。
還可以用 getDay 或 getUTCDay獲得星期,從零開始,星期天爲 0。星期一爲1。
也可以利用各類格式顯示日期。例如如下。
TypeScript中的事件是 DOM API(DocumentObject Model),DOM API事件是一套標準的鼠標和鍵盤交互和對象和窗口事件。如下的事件列表並非詳盡無遺。
事件 |
觸發時機 |
onblur |
焦點離開目標組件 |
onclick |
目標組件檢測到鼠標單擊 |
ondblclick |
目標組件檢測到鼠標雙擊 |
onfocus |
目標組件得到焦點 |
onmousedown |
目標組件檢測到鼠標按下 |
onmousemove |
目標組件檢測到鼠標指針移動 |
onmouseover |
鼠標指針通過目標組件 |
onmouseout |
鼠標指針離開目標組件 |
onmouseup |
目標組件檢測到鼠標button鬆開 |
事件 |
觸發時機 |
onkeydown |
目標組件鍵盤按下 |
onkeypress |
目標組件鍵盤按鍵按下並鬆開 |
onkeyup |
目標組件鍵盤松開 |
事件 |
觸發時機 |
onload |
對象載入(文檔或圖像等) |
onresize |
對象尺寸改變 |
onscroll |
一個文檔滾動 |
onunload |
文檔關閉 |
事件 |
觸發時機 |
onchange |
目標輸入框的內容改變 |
onreset |
表單重置(清空) |
onsubmit |
表單提交 |
TypeScript中本身定義事件和DOM內置事件採用一樣的機制公佈和偵聽。不論什麼事件都可以有多個偵聽器和多個公佈者。如下是一個偵聽本身定義事件的樣例
如下的類公佈本身定義的事件:
事件依照他們被註冊的順序運行。這個因素很是重要,因爲事件處理程序是序列運行。不是同一時候,順序會影響邏輯。
當你註冊兩個事件偵聽器,並且第一個運行了5秒鐘。
那麼第二個就不會運行。一直要等到第一個運行完畢以後纔會被運行。假設第一個偵聽器出錯,第二個仍是會運行。
你可以用setTimeOut時間參數爲零的方式去運行你的長時間操做。這至關於多線程。就不會堵住其它的事件運行了。
TypeScript捆綁了那些常用對象和方法。因爲Web標準在不斷的演變,你可能會常常發現一些新的東西尚未被包括在標準庫中。你可以查看你計算機上的TypeScript標準庫文件。他在SDK文件夾中,一般多是:
C:\Program Files (86) \MicrosoftSDKs\TypeScript\lib.d.ts
你永遠也不該該改動這個文件。假設你需要新的定義。咱們可以繼續添加新的定義文件。
如下的是 ClientRectList 在TypeScript庫文件裏的當前定義。
假設要爲 ClientRectList加入一個新的isOrdered屬性,僅僅需簡單的在你本身的程序中加入如下接口擴展程序,就能夠立刻使用。
當它被加入到標準庫時,你本身的擴展會引起一個生成錯誤,到時把它刪除了就能夠了。
當你建立ClientRectList的實例時,你將可以訪問過去的方法,以及獲取新的 isOrdered 屬性。
除了這些TypeScript標準庫的定義。TypeScript沒有捆綁不論什麼其它的框架。
在前一章中。咱們談到了,你可以重建你的各類功能,比方像C#同樣,因爲TypeScript有着豐富的語言功能。你也可以訪問所有現有的 JavaScript 框架,jQuery、Knockout、Angular和其它數以百計的框架。但是他們都用純 JavaScript開發,你不會獲得加強的開發體驗,除非,你有一個匹配的定義文件。幸運的是,有一個項目。他們致力於爲所有的JavaScript框架提供TypeScript定義。Definitely Typed 項目,Github地址是:
https://github.com/borisyankov/DefinitelyTyped
有時你會想使用JavaScript的庫、框架或者工具集。
TypeScript編譯器難以理解這些外部的代碼,因爲他們沒有類型信息。
在程序中使用外部代碼的最簡單方法是聲明一個any變量。TypeScript編譯器贊成any對象調用不論什麼方法於屬性。
這會讓你經過編譯,但是沒有本身主動完畢和類型檢查。
在此演示樣例中的特殊keyword是declare。這會告訴編譯器,這個變量是外部的,這個僅僅是編譯時的keyword。編譯爲JavaScript時會被擦除。
爲了使用外部JS代碼能獲取更佳的開發體驗,你需要提供更全面的定義。
可以聲明變量、 模塊、 類和函數,定義外部代碼的類型信息。
Declarekeyword僅僅需要在定義的開頭使用一次。
假設但願外部代碼可以由TypeScript擴展,定義爲class,不然。定義爲interface。
這兩種定義方法的惟一差異是是否能繼承擴展。這兩種狀況下,類型信息都僅爲編譯時使用。編譯爲JavaScript後都會進行類型擦除。
很是多時候,定義會組成一系列接口。組合成很是大的一幅圖景。你甚至可以爲TypeScript不能實現的JavaScript方法建立定義。
好比。它是例如如下圖這樣作:
在此演示樣例中有一個名爲move的難以想象的屬性,他可以做爲函數使用。這樣一來,咱們就可以聲明差點兒不論什麼的JavaScript代碼, jQuery、Knockout、Angular、RequireJS 和其它的老的JavaScript代碼,讓你能在TypeScript中使用它們而沒必要重寫。
假設在運行時,想要獲得類的名稱。在C#中有反射這樣的方法。但TypeScript沒有明顯的內置方法。
靜態方法 getType,檢查編譯後的 JavaScript 函數。而後提取它的名字,這就是TypeScript中的類的名稱。
這樣的技術的限制是你不能得到包括類和模塊的完整名稱,這意味着:
MyModule.Example和 SomeOtherModule.Example 和沒包裝的叫作Example的類,它們所有的返回字符串均爲Example。
TypeScript內置的定義文件描寫敘述了JavaScript原生對象和函數。
你可以在lib.d.ts庫文件裏查看它們。
你會注意到大部分都是使用interface定義的。不但願你繼承它們。
但咱們仍是可以擴展它們的。
好比。如下是給NodeList對象添加onclick事件。
但是這個代碼會被TypeScript編譯器警告,告訴你,NodeList對象沒有onclick函數。
爲了解決此爲題,你需要在你本身的代碼中加入定義代碼。
這個接口聲明可以寫在不論什麼引用的ts文件裏。