使用Typescript來寫javascript


使用Typescript來寫javascript 

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

與coffeescript的比較

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

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走的是另一條路,通過增加靜態類型,提高程序的可靠性,並沒有從語法層面進行大的改進。

我覺得它們兩者是互補的,如果能把兩者結合起來,既提供靜態類型,又增強語法就太好了,不過這可能在很久以后才有可能出現。

編輯器支持

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

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

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

另外,你還可以使用VS2012+typescript插件,這是官方推薦的。另外喜歡用sublime text2的同學,可到這里下載:https://github.com/raph-amiard/sublime-typescript。另外vim/emacs也有相關的插件也有,eclipse這里似乎還沒有動靜。

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

下載地址

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

先到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成熟一些后再用它。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM