使用Typescript來寫javascript

使用Typescript來寫javascript 

昨天偶然發現idea竟然支持typescript了,因而打算嘗試一下typescript,目前的感受還不錯,相比haxejs,它與angularjs之間的配合要流暢得多。javascript

與coffeescript的比較

Typescript與Coffeescript都是對javascript的改進,但二者走的是不一樣路線。Coffeescript是從語法的角度,經過提供相似於python/ruby的語法,讓代碼寫起來更加簡潔,可讀性更好。而且它提供的一些控制結構,能夠避開Javascript中的問題,好比for ... in ...,使用coffeescript可讓多層嵌套看起來不那麼痛苦:html

self.validate json, (err, json) -> if err then cb(err) else self.mapFiles json, (err, json) -> if err then cb(err) else self.addFields json, (err, json) -> if err then cb(err) else self.store.create json, cb

回調的參數放在右邊,看起來就像是把前面函數的返回值放在了右邊供調用,讀起來比較輕鬆。習慣於python/ruby的開發者可能會比較喜歡coffeescript,我也以爲它在這方面很好。前端

而Typescript走的是另外一條路,經過增長靜態類型,提升程序的可靠性,並無從語法層面進行大的改進。html5

我以爲它們二者是互補的,若是能把二者結合起來,既提供靜態類型,又加強語法就太好了,不過這可能在好久之後纔有可能出現。java

編輯器支持

Idea並無聲明已經支持typescript,沒有看到相關插件,在建項目時也沒有任何提示,只有在建立一個以.ts結尾的文件時,它能夠識別出來,說明這仍是一個實驗性的功能,沒有徹底完成。不過通過個人試用,基本功能都有了:node

  1. 代碼高亮、格式化
  2. 代碼提示
  3. 錯誤檢查

其中編譯爲js文件的功能彷佛有點問題,另外module()函數的檢驗有時候也不太對,不過這並不影響咱們的使用。儘管仍是一個開發中的功能,但已經跟idea對haxe的支持不相上下了。python

另外,你還可使用VS2012+typescript插件,這是官方推薦的。另外喜歡用sublime text2的同窗,可到這裏下載:https://github.com/raph-amiard/sublime-typescript。另外vim/emacs也有相關的插件也有,eclipse這裏彷佛尚未動靜。jquery

聽說typescript提供了一些服務性質的api,可讓IDE實現功能(如代碼提示等)更加容易。c++

下載地址

官方地址:http://www.typescriptlang.orggit

先到nodejs網站下載並安裝nodejs,而後運行如下命令:

npm install -g typescript

其中npm是由nodejs提供的包管理工具,安裝nodejs後就直接可用了。

相關資料

Typescript相關的資料很少,目前官網上僅有一些示例和簡單的文檔,我收集的有如下這些連接

  1. 官網:http://www.typescriptlang.org/[示例][在線嘗試][官方例子源代碼]
  2. 語言規範:http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf,可用來參考,很難看懂

語言特性

Typescript官方的文檔很簡單,只給出了一個簡單的例子,沒有詳細的文檔來說解各功能,因此對於初學者入門仍是稍有難度,我花了很多時間才基本會用,比我預想的時間多了不少。

難度不是在語言特性上,而是在代碼的組織和示例上,實在太少。好比如何調用聲明文件,如何按module來組織代碼等,對於初次接觸typescript的人還須要一些時間理解。

我對Typescript的印象,可總結爲如下幾點:

  1. Typescript的思路仍是Javascript,只是對它進行了加強
  2. Typescript能夠看做是Javascript的超集,全部javascript代碼能夠直接寫在typescript中,這一點對於Javascript程序員來講很方便
  3. Typescript中增長了靜態系統,提供了class/interface/method的支持。類型系統可讓咱們在編譯期驗證代碼是否有筆誤,也能夠得到編輯器的提示
  4. Typescript以module方式來組織代碼,既支持commonjs的方式,又支持amd的方式
  5. Typescript中的文件可分爲實現文件和聲明文件,有點像c++中的.cpp和.h的關係。
  6. Typescript便可用來寫後臺代碼,也能夠用來寫前臺代碼,初步感受彷佛更偏後臺一些

Class/Interface/Method

Typescript中提供了class、interface關鍵字,可將代碼以類的方式組織在一塊兒。構造函數爲constructor,靜態方法前要加static。在實例方法中調用靜態方法時,必須在前面加上類名。這塊看起來比較普通,很好理解。

class User { constructor(name:string, age:number) { } hello() { alert("hell, " + name); User.test(age); } static test(n:number) { console.log("this is a static method, argument: " + n); } }

module

這塊對於非Javascript程序員來講,有點不太好理解。我以前雖然學習了一段時間的nodejs,但如今已經快忘光了,這幾天拾起來的時候,仍是很痛苦的,資料不多。如今只能算是有所瞭解,講的可能不全對。

Javascript在語言層面沒有提供模塊機制,當代碼多了之後,很難組織起來。好比在服務器端,將不一樣的功能分在不一樣的文件中以複用,或者在瀏覽器端,想把一個大文件分紅多個,按需下載。但文件之間依賴關係、如何向外暴露對象供使用等,都沒有規定。因此人們作了不少嘗試,制定了一些規範來解決這個問題,其中比較有名的有兩個名詞,一個commonjs,一個是amd。

關於它們的介紹和相互關係,能夠看這篇文章,說得很好:http://www.udpwork.com/item/3978.html

簡單地說,commonjs是一套規範,它定義瞭如何將代碼組織爲模塊,如何向外暴露對象,若是依賴。在導入模塊時,又能夠分爲同步和異步,通常可認爲commonjs表明同步,AMD表明異步。服務器端代碼更須要同步,瀏覽器那邊更須要異步,在typescript的編譯器中同時支持這兩種方式。但commonjs是默認的,因此我前面說感受它更偏向後端。

當咱們在代碼中使用了module()函數時,Typescript能夠把它們編譯爲commonjs或amd方式的調用。好比:

/// <reference path="./libs/underscore.d.ts"/> import _ = module("underscore");

當咱們這樣編譯時:

tsc test.ts

它會生成這樣的js代碼:

var _ = require("underscore");

當咱們指定爲amd時:

tsc --module amd test.ts

它會生成這樣的代碼:

define(["require", "exports", "underscore"], function(require, exports, _____) { var _ = _____; });

在服務器端咱們通常用前者,在瀏覽器端通常使用後者(或者徹底不用module)。

不使用Module

若是咱們在typescript使用了module函數,則生成的代碼在瀏覽器端執行時,須要有一些script loader的支持。對於瀏覽器端代碼,咱們通常生成amd風格的代碼,因此須要找一個支持amd的庫放在前端。這樣的庫有不少,好比:

  1. RequireJS
  2. Nodules
  3. JSLocalnet
  4. curl.js

可根據本身的須要使用。我嘗試了RequireJS,不喜歡它的網站風格,寫了那麼多,但老是沒說重點。好比關於它的配置,咱們須要面對的第一個問題,但是它就是沒給一個示例出來,讓我在網上處處找別人寫的例子。要想用好它,可能得先好好讀它的文檔,再到網上找別人的代碼看。

因此最後我仍是按照傳統的方式來組織代碼,在typescript中徹底不使用module函數,而用了全局聲明的方式:

/// <reference path="../../libs/underscore.browser.d.ts"/> declare var _:UnderscoreStatic;

而不是

/// <reference path="../../libs/underscore.browser.d.ts"/> import _ = module("underscore")

當使用declare var來聲明某變量時,即假設它已經在全局中可用,後面的UnderscoreStatic則是在underscore.browser.d.ts這個文件中定義的,關於underscore提供的全部方法的接口描述。

使用這種方式時,咱們保證這段js在瀏覽器端運行時,已經導入了underscore.js。咱們能夠按照傳統的方式來引用js文件:

<script src="http://freewind.me/blog/20130128/.../jquery.js"></script> <script src="http://freewind.me/blog/20130128/.../underscore.js"></script> <script src="http://freewind.me/blog/20130128/.../myapp.js"></script>

Headjs

若是你既不想用requirejs等庫,又想異步下載js文件,能夠考慮這個庫:http://headjs.com/

headjs不支持commonjs/amd,它有本身的異步下載的api,十分簡潔好用。配合我上面的declare var方式,很好用。

這裏給出一個簡單的例子(headjs+jquery+underscore+angularjs+本站js):

<script src="http://freewind.me/public/javascripts/head-0.99.min.js"></script> <script type="text/javascript"> head.js('/public/libs/jquery-ui-1.8.24/js/jquery-1.8.2.min.js', '/public/libs/jquery-ui-1.8.24/js/jquery-ui-1.8.24.custom.min.js', '/public/libs/underscore/1.4.3/underscore.min.js', '/public/libs/bootstrap-2.1.1/js/bootstrap.min.js', '/public/libs/angular-1.0.2/angular.min.js', '/public/libs/angular-ui-0.3.2/angular-ui.min.js', '/public/libs/marked.js', '/public/libs/slickswitch/js/jquery.slickswitch.js', '/public/libs/html5.js', '/public/libs/flot/0.7/jquery.flot.js', '/jsRoutes.js', '/public/libs/moment/1.7.2/moment.min.js', '/public/javascripts/angular-config.js', '/public/javascripts/app.js'); head.ready(function () { app.value("AdminCommonData", { "menuTree": [], }); }); </script>

head.js()方法中,可傳入多個js路徑,它們並行下載,但按照聲明順序依次執行。能夠把一個head.js()分開寫成多個。head.ready()方法將會在全部js下載完成後執行裏面的回調函數。

headjs有一個很是好用的特性,便可以把head.js()放在head.ready()的後面:

head.js(".../a.js"); head.ready(function() { console.log('I'm ready')}); head.js(".../b.js");

其中的head.ready()函數,雖然寫在"b.js"前面,但仍是會等到b.js下載並執行完後,纔會執行。

若是咱們的模板引擎使用了繼承關係,則該特性頗有用。好比在play中有一個layout.html文件和內頁main.html,咱們能夠這樣組織代碼。

layout.html

<html> <head> <script src="http://freewind.me/public/javascripts/head-0.99.min.js"></script> <script type="text/javascript"> head.js('/public/libs/jquery-ui-1.8.24/js/jquery-1.8.2.min.js' // 全部全局通用的js放在這裏); </script> </head> <body> #{doLayout /} </body> </html> <script> head.ready(function() { // 最後執行的啓動代碼放在最後 }); </script>

main.html

#{extends "layout.html" /} <script> head.js(".../main.js", // 僅在本頁中使用的js文件) </script> <div> ... </div> <script> head.ready(function() { // 能夠在這裏爲layout.html最後的函數調用準備數據 }); </script>

在這裏要補充一句,若是你使用angularjs+headjs的話,不能在<html>上聲明ng-app="xxx",而應該在layout.html中最後的函數中調用:

<script type="text/javascript"> head.ready(function () { angular.bootstrap(document, ["MyModule", "MyAnother"]); }); </script>

注意必定要把<html>上的ng-app去掉。我以前嘗試把它們兩個結合使用老是失敗,此次終於找到緣由。

聲明文件

若是你仔細看了前面的例子,會發現有一些以三個斜槓開頭的代碼,如:

/// <reference path="../../libs/underscore.browser.d.ts"/>

它是一種註釋,告訴typescript編譯器,當前文件使用了哪些聲明文件,以幫助編輯器提示信息,及編譯器檢查類型。這種註釋很重要,若是後面的路徑不對,則編譯會失敗。

引用的文件以.d.ts結尾,它們是一種聲明文件,就像是c語言中的header文件,只包含了類或函數的簽名,而沒有實際內容,用於編輯器提示和編譯器驗證。它們的內容形如:

declare interface UnderscoreVoidListIterator { (element : any, index : number, list : any[]) : void; } declare interface UnderscoreMemoListIterator { (memo : any, element : any, index : number, list : any[]) : any; } declare interface UnderscoreListIterator { (element : any, index : number, list : any[]) : any; }

這種文件很重要,由於咱們要想使用第三方的js庫,通常都須要手動作出這樣的聲明文件,才能在typesafe的環境中使用它們。只須要以註釋的方式寫上便可,不須要在實際代碼中聲明或引用什麼。

如今在idea中,這些引用還得手動去寫,不太方便,我想tsc或者編輯器應該會對它進行加強。

下載第三方js庫的聲明文件

如今有不少優秀的第三方js庫,咱們要在typescript中使用它們,難道要一一手動建立這些文件嗎?這個工做量可不小。

好在已經有人這麼作並把成果開源出來了,咱們能夠直接下載它們,放在本身的項目中,再加上引用註釋便可。

包含幾乎所有的:https://github.com/borisyankov/DefinitelyTyped,你應該把它clone到本地。

使用方法:把它們clone到本地,放在某個地方(好比工具目錄中),而後在咱們本身的typescript中添加以一個引用便可:

/// <reference path="../../libs/AngularTS.module.d.ts"/> /// <reference path="../../libs/underscore.browser.d.ts"/>

建立本身的聲明文件

若是咱們用typescript寫了一些模塊,想讓別人調用,除了把整個代碼複製給他之外,還能夠生成一個聲明文件(.d.ts),讓他使用該聲明文件便可。tsc提供了選項讓咱們生成.d.ts:

tsc --declaration my.ts

若是一切正常,將會在當前目錄下產生一個my.d.ts文件,裏面包含了my.ts中定義的代碼的接口聲明。

類型

Typescript在javascript的基礎上提供了類型系統,但它同時也支持動態類型。若是咱們把一個變量或者返回值聲明爲any,則它表示「動態類型」,編譯器不會檢查它的類型信息,但咱們也得不到編輯器的提示信息。

any

function(obj: any) { obj.non_exist_method(); }

如代碼中所示,obj被聲明爲any,雖然內部調用了一個不存在的方法,編譯器也不會提示有誤,只有在運行期才知道。

推斷

雖然typescript支持靜態類型,但咱們並不須要像java那樣,在每一個地方都要聲明類型,由於typescript能夠推斷:

var name = "Freewind";

則name會被認爲是string類型.

function myname(name:string) { return name; }

則myname函數的返回值被認爲是string類型。

但咱們在聲明的地方,最好仍是加上類型,之後看起來會比較清楚。

經常使用類型

any 可表示動態類型
string 字符串
number 數字
bool true或false
null null
undefined undefined
void void
string[] 字符串數組
{a;b;} 等於{a:any; b:any;}
{ a:string, b: number; }  
{ a:string, ()=>number; } 後面那個是函數
() => void 表示形如 function() {} 這樣的函數
(string) => number 表示形如 function(name:string) { return 10; } 這樣的函數
{ [string]: number; } 表示一個object,它的key爲string,值爲數學,形如:

{ "aaa": 111, "bbb": 222}

語法糖

this.filter((todo: Todo) => todo.get('done'));

使用grunt自動編譯typescript

若是你使用的編輯器還不支持自動編譯typescript,可使用grunt來編譯,還能夠進行更多的任務,如對產生的js進和合並、壓縮等工做,十分方便。

具體可參看這篇文章:在Java項目中擁抱Nodejs — 使用gruntjs編譯typescript,並將生成的js合併、壓縮

Typescript待實現的特性

僅以目前的typescript來講,雖然在類型方面作的不錯,可是語言自己還有不少能夠改進的地方。好比簡化語法,提供更好用的控制結構,對異步進行更好的支持等。若是能夠在語言層面改進javascript那些容易出錯、不方便的地方,我想會有更多人採用typescript。

從這裏的issue列表中,能夠看到呼聲較高的特性有:

  1. 支持泛型
  2. 實現await關鍵字:可讓異步調用變得簡單
  3. for … of:js內置的for(x in y)對於數組來講,容易產生問題,但咱們又須要常常遍歷數組,因此提供for of代替
  4. 內置壓縮js的功能
  5. protected關鍵字
  6. 生成xml格式的文檔
  7. extension methods:可讓咱們的函數調用更加流暢,好比underscore的那些方法 ,就能夠寫成 array.filter(function(item) {return ..})
  8. allow module.exports
  9. string interpolatio and block strings
  10. support for abstract classes
  11. macros:利用macro,能夠擴展語言自己

其中我對2,3,6,10,11這幾項很感興趣,若是它們實現了,則typescript的吸引力會大大加強。

TypeScript展望

對於Typescript的將來,我仍是比較看好的,由於對於服務器端的編程,類型系統是很重要的,可讓咱們的代碼質量變得更高,讓不一樣版本之間的庫的兼容性也更好。

我以前使用nodejs感受很鬱悶的一點是,某一個庫升級了(如改變了api接口),則相關的庫都出錯了,而想要找出問題很難,只能經過單元測試找到問題,再查看文檔解決。而這樣的問題在java中出現的就比較少,由於有類型系統的保證,若是接口改變了,直接編譯都會出錯。

使用typescript後,讓nodejs代碼也具備的這樣的能力,對於社區的前進是頗有幫助的。並且對於java/c#程序員來講,這是頗有吸引力的。

隨着之後typescript相關編輯器、工具的成熟,能夠預見它將和coffeescript同樣,成爲javascript開發人員的標準備選,也許會有一些庫和工具直接支持typescript,那樣的話就會有更多人來使用typescript了。

若是你對typescript也感興趣,歡迎加入QQ羣:Typescript熱情交流羣(250299804)

相關工具還不夠成熟

通過幾天的試用,發現如今使用它也仍是不夠方便。主要有幾下幾點:

  1. Idea的編輯器支持typescript的高亮、格式化及代碼提示,但還不能像java那樣,實時顯示錯誤,因此只能藉助第三方工具(好比gruntjs)來編譯並顯示錯誤。這種方式對於一個靜態類型的語言來講,不是很方便,由於類型方面的錯誤很容易犯,若是分紅兩步走,來回切換工具會讓定位很麻煩。
  2. 我使用的是gruntjs,它基於nodejs,有typescript插件能夠編譯typescript文件。可是因爲nodejs在windows平臺上的有一個bug致使沒法全面取得錯誤信息,當ts文件有錯誤時,只能取到第一行(錯誤行號),而拿不到具體信息。有時候怎麼看那一行都不知道錯在哪兒,只好又另開一個cmd,調用tsc命令去編譯,很麻煩。
  3. 更麻煩的事情來了。在gruntjs中編譯提示有誤,但我手動調用tsc編譯又沒錯,調試好久都不找不到緣由。gruntjs就無法用了。
  4. Typescript不支持將多個ts文件合併生成一個js並壓縮,因此我只能借助gruntjs工具實現,但gruntjs又有點問題。
  5. Typescript語言自己還存在一些問題。好比array明明有length屬性,但不能被識別,致使明明與它匹配的接口會編譯出錯。另外,一個any[]類型的數組,應該能夠包含各類對象,但放入一個string和一個函數後,提示有誤,必須在string前加一個<any>(多是由於Typescript目前還不支持泛型,致使推斷有誤)

綜上所述,目前使用typescript進行前端開發,問題還比較多,各類小問題都會影響開發效率。因此看樣子,只能老老實實地使用javascript(或者coffeescript?),等到typescript成熟一些後再用它。

相關文章
相關標籤/搜索