TypeScript與Haxe:兩種截然不同的JS轉譯工具橫向對比
JavaScript無疑是當今最火爆的編程語言之一,它的崛起要歸功于AJAX、Node.js的出現以及時下各種MVC框架的流行。但作為一門在十天之內創建出來的語言,JS本身存在著一些不完善之處、以及容易令人疑惑的地方,例如不支持強類型以及new關鍵字的用法。尤其與當前主流的面向對象語言以及動態語言相比,其不足之處顯得尤為突出。
為了克服JS語言中的缺陷,讓更多的人能夠編寫出優秀的代碼,市面上出現了 大量 能夠將其它編程語言轉譯為JS的工具。其中較知名的有 CoffeeScript 、 Dart 、 GWT 、 Script# ,以及本文的主角 TypeScript 與 Haxe 。
TypeScript是這一領域中的新貴,由微軟于2012年發布。近期發布到1.5版,加入了大量ES6的特性。TypeScript被設計為 JS的一個超集,因此現有的JS都是合法的TypeScript代碼。這門語言也得到了Angular團隊以及Telerik的支持。
作為這一領域中的老前輩,Haxe從2006年起就提供了轉譯為JS的功能。與TypeScript類似,Haxe也提供了一套類似于JS的語法、靜態類型系統以及模塊。除了JS之外,Haxe還能夠提供編譯為Flash、PHP、C++等語言的功能。
來自Haxe的員工Andy Li最近在 博客 中對TypeScript與Haxe進行了一次全面的比較,包括語法、底層語義、類型系統、以及組織和生成代碼的方式。為開發者如何在這兩種工具間進行選擇提供了一個不錯的參考。
語法
TypeScript與Haxe的基礎語法是非常相似的,它們都支持在定義變量時聲明類型。但Haxe在設計時引入了函數式編程的概念,因此Haxe中的大部分語法元素其實都是一種表達式,可以代表某個值。
// It is evaluated as the last expression inside it. // Here is an example that use try-catch expression together with block expressions. var result = try { var a = computationThatMayThrow(); finalComputation(a); } catch (exception:Dynamic) { rollBack(); defaultValue(); }
另一個不同點表現在兩者處理分號的不同。TypeScript會自動在行末添加分號,因此不強制開發者使用分號。但這在Andy看來是一種 極端錯誤 的做法,它可能會帶來意料之外的后果。而Haxe強制要求開發者在行末使用分號。
其它的一些語法差別還包括:
- 兩者都支持可選參數與剩余參數,其語法有細微的差別。
- TypeScript支持ES6中的箭頭函數表達式,而Haxe的創造者 Nicolas Cannasse 則認為Haxe本身就具有表達式的特性,并且可以通過使用宏的Haxe類庫實現相同、甚至更簡潔的語法。
- 兩者對于函數返回類型的定義方式不同,TypeScript使用的語法與箭頭函數相類似,而Andy認為Haxe的語法有 令人誤解之虞 。
- 兩者在類與接口的定義語法上略有不同,TypeScript提供了一種名為“參數屬性”的語法糖以簡化類的編寫,而Haxe則需要通過宏才能夠實現相同的語法。
- 兩者定義屬性的getter與setter的語法截然不同,TypeScript的方式較為直觀,而Haxe的語法則相當獨特。
Andy認為總的來說,兩者的語法具有高度的相似性,TypeScript本身提供了許多用以簡化代碼的語法,而Haxe則可通過宏進行擴展,使特定表達式的語義產生轉變。
語義
雖說TypeScript與Haxe在語法上非常近似,但它們在語義方面有著相當大的差距。首先兩者對于變量的范圍有著完全不同的定義,TypeScript的變量范圍與JS相同,通過var定義的變量作用于整個函數,而Haxe的變量范圍與現代語言更為接近,變量的范圍作用于一個代碼塊。
// TypeScript { var a = 1; } console.log(a); // ok, because `a` exist outside of a block // Haxe { var a = 1; } trace(a); // error: Unknown identifier : a
在Andy看來,塊范圍的做法更適合于塊結構的語言。實際上,JavaScript的作者 Brendan Eich 也承認,當初選擇以函數作為變量范圍的做法是因為 時間緊迫 。
其它一些語義方面的差別還包括:
- TypeScript對于this的處理方式與JS完全相同,在函數中的this具體指代的對象是動態決定的。而Haxe的作法與主流編程語言相似,this總是指向當前類的某個實例。
- 兩種語言都定義了枚舉類型,但TypeScript中的枚舉只是一組有限值的集合,而Haxe中的枚舉是一種強大的函數式編程結構,稱為 泛代數數據類型(GADT) 。它更像是一種類型的有限集合,具有更為強大的處理方式。
- 在switch語句方面,TypeScript的處理方式與傳統的C風格相同。而Haxe的switch實際上實現了函數式編程中的 模式匹配 。但 TypeScript支持ES6中的解構賦值特性 ,能夠一定程度上代替模式匹配的作用。
Andy總結道,TypeScript選擇盡可能遵循JS標準的語義,因此如今的JS開發者能夠很容易上手。而Haxe的做法是盡可能修復 JS的設計缺陷 ,遵循當今主流語言的設計方式,因此對于不具備JS開發經驗的用戶更具吸引力,并且Haxe中引入了部分函數式編程的概念,它很好地與類似于JS的語法相容。
類型系統
TypeScript與Haxe的基本類型非常接近,后者只是多了一個Int類型。兩者都支持自定義類型,TypeScript中可以使用類/接口及枚舉,而Haxe中還多了typedef與abstract兩個選擇。
以下是Andy對兩者的類型系統進行的一些具體比較:
- TypeScript使用了一種結構化的系統,所有類型都可以被表達為某個接口,只要是具有相同屬性的類型都可以兼容。而Haxe的類型較嚴格,無法將匿名對象賦給某個類型化的變量。
- 在Haxe中,Bool/Float/Int之間不可進行隱式轉換,除非使用 abstract 。
- 兩者都支持編譯時類型推斷,但Haxe要更強大一些,它能夠從某個var變量首次使用時的賦值推斷出其類型,而不僅僅限于其初始化時的值。
- TypeScript對于靜態類型采取了比較寬容的態度,即使在代碼中出現類型錯誤,依然能夠編譯為JS代碼。而Haxe會直接報錯,除非使用untyped關鍵字。
- TypeScript中有一些不嚴謹(但具有一定實用性)的設計。例如函數參數本應是逆變的,但 在TypeScript中卻是雙變的 。
Andy總結說,Haxe的類型系統比TypeScript更為嚴格,但后者有時能夠簡化我們的工作。不過在他看來,如果出現了明顯的類型問題,應當中止編譯過程。Andy認為這種寬容會造成開發者的濫用,這方面的一個例子就是IE,它對語法錯誤的寬容讓開發者逐漸地忽視了語法錯誤與Web標準。而TypeScript很不幸地繼承了微軟的這一傳統……
代碼組織與生成
TypeScript的代碼組織方式與JS一樣靈活,在一個.ts文件中可以包含語句、函數與類的定義,還可以使用三種 模塊系統 ,可以導出變量、函數與類型。
Haxe的代碼組織方式更像靜態語言,不允許在頂層出現表達式(包括函數),只能包含在類型(主要是類)中。Haxe程序的入口方法是某個用戶指定類中的靜態main函數。
在文件組織上,TypeScript可以將.ts文件放置在任意位置上。而Haxe的做法類似于Java,其文件結構必須對應于包與模塊的層次。
在代碼生成時,Haxe能夠進行較多的高級優化功能,例如刪除不可訪問的代碼,對函數進行內聯等等。而TypeScript中的優化功能比較單一,主要是將一些高級語法特性轉譯為ES3/5所支持的JS語法。
Andy在總結中表示,從代碼的組織與生成來說,TypeScript表現出較高的靈活性,更接近于JS。而Haxe借鑒了主流編譯型語言的經驗,對于文件目錄要求較嚴格。
結論
在文章的最后,Andy對他的觀點進行了一番總結。他認為這兩門語言具有一定的相似性,但在設計思想上有許多不同之處。TypeScript總體更接近原生的JS,對于JS開發者來說更容易上手。而非JS開發者或許會喜歡Haxe,它更接近于現代的靜態語言,同時引入了許多函數式編程的概念,進一步加強了它的語法特性。
Andy個人更偏向于使用Haxe,除了因為他本身就是Haxe Foundation的一員之外,還因為Haxe能夠提供編譯為其它語言的功能,而這一點是TypeScript所不具備的,因為后者設計時就是為了編譯為JS這個目的而生。