轉入 TypeScript 做開發有大半年了,想來分享一些自己的心得體會。
第一次學習并使用 JavaScript 大概是在07年,當時為了做一個 Web 自動化的項目,踏上了我的 JavaScript 之旅,最終以《Web 應用自動化生成工具的設計與實現》(大概是這么個名字)這篇論文來結束了我的研究生生涯。
那時候還沒有全棧開發這一說,不過想想自己似乎10年前就開始了這種工作狀態。我過往的技術經歷比較復雜,在我們那個年代最主要的軟件還是以 Windows C/S 架構為主,搞過 Socket 通信、寫過 ARP 病毒,研究過 MFC 的 Thunk 技術;后來慢慢又接觸了 C#、Java,還深深記得剛從 DAO 轉入到 Hibernate 時的那種懵逼。
那時候覺得學什么都特別的開心,計算機專業學的還是“科學”與“技術”,也沒有“互聯網行業”這一說,我們這一行都還叫做“軟件行業”,培訓班都還在教你如何做網頁?!稌运善嬲劇防锩嬗幸患v歌壇那個“白衣飄飄的年代”,我覺得2000年-2010年這10年就似乎是我的那個“白衣飄飄的年代”。那時候技術還比較神秘,大家都以研習 Windows Undocumented API 為榮,我記得我還打印了一本 Intel x86 匯編的小冊子隨身攜帶;那時候以為掌握了 Hook 就掌控了操作系統,以為會用 Ethereal 就監聽了全世界。
我記得那是一個午后,我的導師花了兩個小時給我講解 JavaScript 的基礎語法。這應該算是我從業多年來,最快速最密集學習的一門語言了。我還依稀記得那“22條軍規”:
- 變量類型有 numer、string、bool......
- 定義變量用 var
- if & for 跟 C 語言 一樣
- function 跟 C 語言 一樣
- class 的語法是...
- 訪問成員變量必須帶上 this 指針
- 閉包是......
- ......
就這么從頭到尾講完了這10來條語法,然后導師對我說:“恩,差不多就這些了,你先開工吧,明天我來檢查......”。WTF?。?!就像駕校司機剛告訴了你如何啟動汽車、如何操作剎車、油門,就對你說:“差不多了,明天你開車去趟......”。好吧,一個“老司機”就這么上路了,反正寫啥都是在寫 BUG。
作為那個年代的三大神獸語言 C++、C#、Java,我基本上都同時在寫。不過他們都是 Class First 語言,以面向對象為核心,類是一等公民,定義一個類也是各種繁文縟節(相對于動態語言來說)。所以我在第一次見到 JavaScript 這種動態語言時,我的內心是震撼的,猶如從三維世界進入到四維世界的那種“宏大”,那種難以描述的“自由”。
為了防止有人噴我,特地做一下說明,不然一堆屁話就來了:
1、回頭來看,我如此的震撼并不是因為 JavaScript 語言本身的精妙,而是因為動態語言相對于靜態語言的自由程度;
2、我之前的技術經歷主要是在 C++,并未接觸過動態語言;
3、JavaScript 語言也有很多糟粕,不算“優秀”的語言,但并不影響我對它的喜愛;
4、Ruby 也是我喜愛的語言之一。
一個 JavaScript 的類(準確的講是指模擬 C++ 里同一概念的類),它身上的屬性、方法可以被動態的添加、刪除、修改;一個函數可以作為變量到處傳遞,還有 lambda 表達式,可以直接寫在函數的參數里面作為 Callback,這在當時的 C++/Java 里面簡直不可想象。
在 C++ 里面,底層的很多網絡庫都是 C 語言實現的,所以一個 C++ 的成員函數是不能直接直接做為 Callback 函數傳給底層 lib 的,而是要傳遞一個類的 static 方法,然后再想辦法從這個 static 方法 跳回到成員方法。MFC 的 Thunk 技術就是在解決這一問題。直至今天在 Java 里面要實現一個 Callback 還需要寫一大堆 Interface、Listener 什么的,真的是非常繁瑣(Java 8 已經支持 lambda 表達式了,通過引入編譯工具可以在 Android 開發里面使用,強烈推薦)。
而最最重要的一點就是 JavaScript 的世界觀其實就是一張 Hash 表,至少我的理解是這樣的。這就是一個 key-value 的世界,所有類、對象都是 Hash 表,數組也是。key 就是對象屬性,value 可以是普通變量,也可以是函數,也可以是一個對象,等等。只是恰好如果 value 是一個變量,我們習慣稱它為成員屬性,如果 value 是一個函數,我們習慣稱它為成員函數。而 Prototype 原型鏈也只不過是 Hash 表的一種存儲結構,原型鏈查找也就是一種 Hash 查找算法而已。
10年前,用了一個下午愛上了 JavaScript,而10年后又遇上了 TypeScript。這10年中大部分時候還是在用 C++ 和 Java 來工作,所以 JavaScript 的一些痛苦,感受并沒有像 “職業”開發者那樣深,不過也還是遭遇到了不少的坑。變量不用定義就能用,偶爾代碼寫錯一個字就成了一個新變量,為此卻要付出大量的 debug 時間;早期調試也相對比較痛苦,這幾年工具越來越完善了,不過做為一個從 Visual Studio 入門的開發者,看別的平臺的調試方法總覺得不夠傻瓜。
遇上 TypeScript 雖然沒有10年前遇上 JavaScript 那樣轟轟烈烈,但是也算喜愛有加。遇上 Javascript(遇上動態語言)我覺得解決的是精神生活的問題,而遇上 TypeScript 算是解決了物質生活的問題。以前開發 JavaScript 時的部分臟活累活都可以被消滅了。
這里才剛剛開始
先簡單科普一下 TypeScript:
TypeScript 是由微軟開發的一種基于 JavaScript 語法的語言,且已經支持了ES6、ES7語法。你可以理解它為 JavaScript 的超集,也可以理解為 JavaScript 的增強版。TypeScript 代碼不能直接運行,需要通過編譯器編譯成 JavaScript 文件才能使用,所以依然可以在瀏覽器環境或者 node 環境下無縫使用。
好了,跑題了這么久,我終于要說為什么要從 JavaScript 轉到 TypeScript 了,它到底解決了什么問題。
編譯期類型檢查
最最重要的能力就是:
- 編譯期類型檢查
- 編譯期類型檢查
- 編譯期類型檢查
重要的事情要說三遍。有了編譯期類型檢查再也不用擔心變量寫錯名字、錯誤的類型賦值、寫掉對象屬性這些基本問題了。雖然無時無刻都需要書寫類型信息,但是我相信這是值得的。部分 JavaScript 開發者可能會覺得有點繁瑣,其實相信從靜態語言轉到 JavaScript 的開發者一定會感覺到非常親切,所以主要還是一個先入為主的習慣問題。相對于這些付出,收益是巨大的。尤其在大型項目里面,后面接手的同事再也不用去“猜測”一個 Callback 返回的變量到底是什么類型了。
以下是 JavaScript 代碼:
var name; //定義變量
function getName(type) { //定義函數
return ...
}
class Person { //ES7下的類定義
name = '';
age = 18;
getName() {
...
}
getAge() {
}
}
以下是 TypeScript 代碼:
var name: string; //定義變量
function getName(type: string):string { //定義函數
return ...
}
class Person { //類定義
private name: string = '';
private age: number = 18;
public getName(): string {
...
}
public getAge(): number {
}
}
大家可以發現,最基本的語法其實就是在所有的變量或者函數的定義時,在變量名字的后面加多了一個類型信息。相較于傳統靜態語言的語法,基本上就是把類型信息從前面移到了后面而已。
我們再來看一下調用代碼:
let name: string = obj.getName() //編譯通過
let name: number = obj.getName() //編譯失敗
let name = obj.getName() //編譯通過
let nameOther: number = name //編譯失敗
- 第一行編譯通過,因為類型一致
- 第二行編譯失敗,因為類型不一致
- 第三行編譯通過,因為 name 沒有指定類型,TypeScript 編譯器會自動推導,認為 name 變量也是 string 類型
- 第四行編譯失敗,因為 name 被推導為 string 類型,而 nameOther 是 number類型,類型檢查失敗。
所以其實我們可以看到,TypeScript 也并不是無時無刻都必須要寫上類型信息,只要定義時類型信息足夠豐富,我們也可以選擇性的偷懶。
最近有看到一種言論說:
連 C# 都在拼命動態化,為何要讓 JavaScript 變成一個靜態語言?
其實我覺得這里是有一些誤解的:“靜態類型檢查并不等于靜態語言或者說靜態化”。
動態語言雖然可以在運行期動態改變類型,但是一個變量類型的改變也是受“業務需求”影響的。我相信沒有誰會因為喜歡炫技而在代碼里面不斷改變一個變量的類型信息。而大部分時候,是因為業務的需求再配合語言的動態優勢,可以讓一個變量在不同階段存儲不同類型的數據。
我們回憶一下,其實大部分時候一個變量在它生命周期內依然是同一種類型一直用到銷毀。只有小部分時候有那么幾個變量會偶爾存儲number,偶爾存儲 string;或者在服務器返回的數據里面,根據 errCode,在 json 里面取出余下不同的信息。
所以一言以蔽之就是說:
“即使是動態語言,一個變量在生命周期內,它的類型變化可能性是非常有限的”。
那么 TypeScript 非常好的解決了這個問題,那就是“聯合類型(Union)”。相信有過 C 語言基礎的同學,對 Union 一定不會陌生。Union 就是說一個變量可以時而為 A 類型,時而為 B 類型,到底是什么類型,這個由開發者自行判斷。
這不正好和“服務器返回的 json 數據格式”的情況非常吻合么?我們通過檢查 errCode (在 restful API 下,更提倡用 header 來返回錯誤信息) 來判斷余下部分應該為 Data 域還是是 Error 域。
一個 Union 的變量定義:
let name: number | string;
name = 123; //編譯通過
name = "danney"; //編譯通過
name = true; //編譯失敗
上述代碼編譯成 JavaScript 的 ES5 版本為:
var name;
name = 123;
name = "danney";
name = true;
對比可以發現,其實 TypeScript 完全沒有沒有給你帶來什么學習成本和使用不便,它真的是在幫你,幫你檢查類型、幫你排除那些粗心大意的小錯誤。
除此之外,在 TypeScript 里面還有很多突破靜態類型的方法,比如你可以把一個變量強轉為 any 類型,這樣就不受編譯器類型檢查限制了。所以再也不要誤認為 TypeScript 把 JavaScript 靜態化了。
代碼智能提示
習慣了在 Visual Studio 上的番茄工具,以及 Android Studio 和 XCode 上的智能提示功能,切到 JavaScript 時,真是有一些不習慣,雖然目前幾大主流的 Web 開發工具也有提示功能,但是我覺得還是不夠完善和智能。
畢竟 TypeScript 語言天生是帶有類型信息的,可以完美識別出一個對象的類型,它自身有哪些屬性和方法,然后調用這些方法時,也能智能匹配出它的函數原型。
說到這里就不能不提 Visual Studio Code 了,簡稱 VSCode。VSCode是微軟新一代的輕量級跨平臺開發工具,雖然也叫Visual Studio,但是我覺得和傳統的 Visual Studio 家族工具已經沒有太大關系了。它更像 WebStorm 或則 Sublime Text 這樣的 Web 開發工具,輕量級、跨平臺,支持各種語言或者平臺插件,Debug 也很方便。
用 TypeScript 做開發,基本上 VSCode 就是標配了。VSCode 自帶 TypeScript 插件,完美智能提示,類型推導,讓你的開發效率極大提升。有意思的是 VSCode 和 TypeScript 編譯器也是用 TypeScript 語言開發的,這就像一個雞生蛋蛋生雞的問題。很多年前我就是在想 Java 編譯器也是由 Java 語言開發的,那么最早的 Java 語言又是用什么來編譯的呢?
其實答案很簡單,現代編譯器基本上都不是從零裸寫的,而是先書寫這門語言的“語法描述文件”,然后由“編譯器生成器”來讀取“語法描述文件”,從而生成一個“編譯器”。所以無論是 Java 還是 TypeScript,都是先用別的語言來創建一個最基本的編譯器,再用這門語言來開發自己的新版編譯器。
JavaScript 混合編程
很多同學一定擔心 TypeScript 的生態環境不夠豐富,相關的 lib 不夠多,這一點完全不用擔心。因為 TypeScript 和 JavaScript 可以在一個工程里面混合編程,TypeScript 的文件后綴是 .ts ,JavaScript 的文件后綴是 .js 僅此而已。ts 文件里面依然用 import 或者 require 來引入一個 module。
唯一的問題就是因為引入的 JavaScript 模塊因為沒有類型信息,會導致 VSCode 的智能提示無法使用而已。有智能提示時算是一個補充,沒有智能提示時我覺得也不算吃虧。
不過其實這個問題基本上已經解決了,那就是 TypeScript 在編譯為 JavaScript 時,可以自動產生一個叫做 .d.ts 的文件,它就是 js 的頭文件,和 C 語言的 .h 文件一個道理。所以當一個編譯后的 TypeScript 作為一個 lib 發布時,會附上 .js 和 .d.ts 兩種文件。這樣別人在導入你的 lib 時,也會獲得智能提示功能。
那么對于一個基于 JavaScript 開發的老牌 lib,有沒有辦法也能智能提示呢?答案是肯定的。那就是現在有大量的開發者在為老牌 lib 人肉編寫 .d.ts 文件。并且微軟提供了一個叫做 typings 的工具,可以為你在線查找并下載一個 lib 的 .d.ts文件。typings 和 npm 使用類似,最新的 typings 也已經統一到 npm 工具上了,即使用 npm 就可以下載 .d.ts 文件了。還有最新版的 VSCode 也能做到自動分析你的包依賴,然后后臺自動下載 .d.ts 文件。不用擔心 JavaScript lib 沒有 .d.ts 文件,連微信小程序的頭文件都已經有了。
編譯質量
對于擔心 TypeScript 編譯質量不如人手寫優化的同學,你們也完全不用怕。TypeScript 編譯邏輯主要有兩部分,一部分是去掉類型信息,回歸正常 JavaScript 語法;另外一部分就是翻譯 ES6 和 ES7 語法,我目測編譯結果和 babel 基本上一致,所以 babel 的編譯結果如果你都不擔心,TypeScript 的編譯結果也就沒什么好擔心的了。
但是假如你連整個編譯過程都覺得不能忍受的話,那一定是你寫的代碼太少。你來開發 iOS 和 Android 試試 ?當年 Windows QQ 編譯一次可是要40分鐘啊,我們一般都是點一下編譯按鈕,然后就去吃飯了。記得以前有從 JavaScript 入門的同學轉去做 Android 開發時,狂吐槽分號的問題,我想說:真的是你見識的東西太少??梢詿釔?JavaScript,但是不要讓它蒙了你的眼。
調試
VSCode 提供了完善了調試工具,非常方便。因為我基本上是在 node 環境下開發,Web 調試不好說,不過 node 下再也不用蹩腳的彈到 Chrome 里面去 debug 了。TypeScript 編譯后會自動生成 souremap 文件,所以 debug 時是不會涉及到編譯后的 js 源文件的。就好像 C 語言編譯成匯編后,你 debug 時也不用關注匯編一樣。
所以另外一個需要注意的就是團隊開發時,如果要修復 bug,千萬千萬不要修改編譯后的 js 源文件。因為之前在網上看到有人在吐槽他用 TypeScript 開發的代碼,編譯成 js 上線后,有同學吭哧吭哧拿 js 代碼去修復 bug,還一個勁吐槽原作者。腦子是個好東西,希望他也有。所以一定要避免在一個項目里面有的人用 TypeScript 開發,有的人用 JavaScript 開發。要么全都用,要么就別用。
其他
相比于 CoffeeScript 等變種 JavaScript 語言,TypeScript 語法幾乎保持不變,沒有什么學習難度。說學習曲線陡峭或者覺得團隊內部不好培訓、不好推進的,我真心覺得這個行業的從業者素質可能確實需要提升。
其他幾個不錯的新增語法點:
- TypeScript 提供了 enum ,我個人超級喜歡
- TypeScript 提供了泛型,解決了不少問題
- TypeScript 支持 JSX 語法,做 ReactJS 和 React-Native 開發的同學應該會非常喜歡
差不多就這些了,擁抱 TypeScript 吧,希望你也會喜歡!