認識 TypeScript

發表於2017-03-11

TB1_NDSPVXXXXcUXVXXXXXXXXXX-900-500

這是一個系列文章,一共會有三篇:

  1. [本篇] 認識 TypeScript – 簡單描述 TypeScript 的定位、特點。
  2. TypeScript 語法學習 – 比較文件化的講述 TypeScript 的使用方式。
  3. TypeScript 工程使用 – 講述如何在工程 —— 包括 Node.js 工程、React 工程 —— 中應用 TypeScript。

最近幾個月有幸體驗了 TypeScript,它對大型軟體 —— 大型 JavaScript 軟體 —— 開發來說真是一把利刃。就像我斜後方那個男人一樣,寫了 TypeScript 就不想再寫 JavaScript 了 —— 真乃取其精華去其糟粕啊!

沒想到我一個 Linux 的老使用者 Copyleft 的忠實擁護者,也會像今天這樣喜歡用 M$ 出的東西,17 歲到 19 歲的我一定會鄙視死現在的我。

背景認識

TypeScript 是微軟開發一款開源的程式語言,本質上是向 JavaScript 增加靜態型別系統。它是 JavaScript 的超集,所有現有的 JavaScript 都可以不加改變就在其中使用。它是為大型軟體開發而設計的,它最終編譯產生 JavaScript,所以可以執行在瀏覽器、Node.js 等等的執行時環境。

什麼是型別系統

下面是摘自 《 Types And Programming Languages 》 中的定義:

A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.

第一個重點是 Proving the absence of certain program behaviors,所以我們亦可將型別檢查器看做一個程式推理工具,可以靜態的證明程式成立。

另一個重點是 Classifying phrases according to the kinds of values they compute,對詞語(比如變數)的值的性質進行分類,比如說 TypeScript 中的 InterfaceClass 等能力。

靜態型別系統是什麼

增加靜態這個定語,是為了和執行時的型別檢查機制加以區分,強調靜態型別系統是在編譯時進行型別分析。

JavaScript 不是一個靜態編譯語言,不存在編譯這一步驟。但從 程式推理工具 的角度來看,JavaScript 的配套中還是有不少的,比如 ESLint 這個不完備的 程式推理工具

靜態型別系統與 Lint 工具的關係

我們先看 ESLint 的定義:

Code linting is a type of static analysis that is frequently used to find problematic patterns or code that doesn’t adhere to certain style guidelines.

區別一

同樣強調 Static Analysis,不過更強調 Certain Style GuidelinesLint 工具是一種團隊協作時的風格規範工具。

區別二

靜態型別型別分析Lint 工具 的區別在於 Lint 工具 沒有 Classifying phrases according to the kinds of values they compute

Lint 工具無法基於型別對程式進行靜態分析,但兩者都有基於 CFG (控制流圖,Control Flow Graph)對程式進行分析的能力。比如 TypeScript 的控制流分析、ESLintcomplexity(當你想寫個比較複雜的迭代演算法時,這個規則就是個渣) 規則等。

TypeScript 和 JavaScript 的關係

和一些基於 JavaScript 的激進語言不同(比如 CoffeeScript),TypeScript 的語法設計首先考慮的就是相容 JavaScript,或者說對 JavaScript 的語法做擴充套件。基本上是在 JavaScript 的基礎之上增加了一些型別標記語法,以實現靜態型別分析。把這些型別標註語法去掉之後,仍是一個標準的 JavaScript 語言。

TypeScript 同樣也在做一些新語法編譯到老語法的事情(就像 Babel 做的), 基本實現常用的 EcmaScript Stage 1 以上的語法特性。

型別系統的益處

偵測錯誤

靜態型別分析首要優點就是能儘早的發現邏輯錯誤,而不是上線之後才發現。比如我們在 JavaScript 中經常發生的問題,函式返回值含混。在開發過程中堅信一個函式返回字串,但到了線上接受了真實資料卻返回了 undefined。看似一個簡單錯誤,卻可能給公司造成數以萬計的損失。

看個例子。

脆弱的 JS 啊,執行一下,Ops!

相同的邏輯我們用 tsc 編譯一下(甚至不需要增加任何的型別標註)。直接靜態分析出來程式有一個 undefined

另一個重要的用處是作為維護工具(重構輔助工具),假如我們有一個很通用的函式,在工程裡用的到處都是,有一天我們要在這個函式最前面增加一個引數。TypeScript 中你只需要改那個函式就好了,然後再執行靜態型別分析,所有和這個函式引數不匹配的地方都會提示出來。但是,在 JavaScript 裡,這個改動很有可能被忽略或者漏掉,打包也不會報錯,然後釋出後線上就掛了……

抽象

型別系統的另一個優點是強化規範程式設計,TypeScript 提供了簡便的方式定義介面。這一點在大型軟體開發時尤為重要,一個系統模組可以抽象的看做一個 TypeScript 定義的介面。

用帶清晰介面的模組來結構化大型系統,這是一種更為抽象的設計形式。介面設計(討論)與最終實現方式無關,對介面思考得越抽象越有利。

換句話說就是讓設計脫離實現,最終體現出一種 IDL(介面定義語言,Interface Define Language),讓程式設計迴歸本質。

看個例子。

那我實現上述 Interface 也只需如下進行。

文件

讀程式時型別標註也有用處,不止是說人在讀的時候。基於型別定義 IDE 可以對我們進行很多輔助,比如找到一個函式所有的使用,編寫程式碼時對引數進行提示等等。

更重要的是這種文件能力不像純人工維護的註釋一樣,稍不留神就忘了更新註釋,最後註釋和程式不一致。

更強大的是,可以自動根據型別標註產生文件,甚至都不需要編寫註釋(詳細的人類語言描述還是要寫註釋的)。

首先安裝全域性的 typedoc 命令。

然後我們嘗試對上面抽象的 Interface 產生文件。

然後下面就是效果了。

TB1G_7nPVXXXXX1XXXXXXXXXXXX-935-1277

編寫第一個 TypeScript 程式

這一節會介紹如何開始體驗 TypeScript,下一節開始會介紹一些有特點、有趣的例子。

前置準備

安裝 TypeScript。

初始化工作區。

新建第一個測試檔案。

第一個例子

我們剛才已經新建了一個名為 taste.ts 的檔案,對 TypeScript 的字尾名為 ts,那我們寫點什麼進去吧!

taste.ts

然後執行命令(tsc 是剛才 npm 裝的 typescript 中帶的)。

然後我們得到一個編譯後的檔案 taste.js,內容如下。

可以看到,只是簡單去除了 text 後面的型別標註,然後我們用 node 執行 taste.js

完美執行,讓我再改寫東西看看?

taste.ts

然後再執行 tsc taste.ts,然後就型別檢查就報錯了。這就是 TypeScript 的主要功能 —— 靜態型別檢查。

有趣的例子 – 基於控制流的分析

看一個 JavaScript 的例子。

這是一個簡單的函式,第一個引數 key 用來獲得一個預設值。第二引數 emphasis 為了某些場景下要大寫強調,只需要傳入 true 即可自動將結果轉成大寫。

但是我不小心將 age 的值寫成了數字字面量,如果我呼叫 getDefaultValue('age') 就會在執行時報錯。這個有可能是軟體上線了之後才發生,直接導致業務不可用。

TypeScript 就能避免這類問題,我們只需要進行一個簡單的標註。

tsc 編譯時,邏輯錯誤會自動報出來。媽媽再也不怕我的邏輯混亂了!

有趣的例子 – Interface

JavaScript 的型別我們稱為鴨子型別。

當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。

鴨子型別總是有點損的感覺,不如叫做面向介面程式設計。所以 JavaScript 就是一門面向介面程式設計的語言,TypeScript 中相對應的就是 Interface

接下來看個例子。

使用 tsc 編譯一切完美,那我們嘗試下面的呼叫。

使用 tsc 編譯,報錯了!說沒有傳屬性 gender。不過 height 也沒傳怎麼沒報錯呢?因為height?: number,其中的 ? 表示這個是可選的。

接下來我們試著傳個非 numberheight 試試看。

使用 tsc 編譯,報錯了!string 型別無法賦值給 number 型別。

有趣的例子 – Implements

這也是 Interface 的應用,假設我們有這麼一個 Interface,是某個架構師寫的讓我來實現一種事物,比如榴蓮。

我只需要簡單的實現 Eatable 即可,即 implements Eatable

如果我刪掉 flavour 的實現,那就會報錯了!說我錯誤的實現了 Eatable

有趣的例子 – 函式過載

什麼過載啊、多型啊、分派啊,在 JavaScript 裡都是不存在的!那都是都是我們 Hacking 出來,Ugly!

TypeScript 對函式過載有一定的支援,不過因為 TypeScript 不擴充套件 JavaScript 的執行時機制,還是需要我們來處理根據宗參分派的問題(說白了就是執行時型別判斷)。

下面是 TypeScript 文件中的一個例子。

這樣至少在函式頭的描述上清晰多了,而且函式的各個分派函式的型別定義也可以明確的標記出來了。

最後

第一篇就先這樣簡單介紹 TypeScript 吧,詳細的語法、工程應用接下來的篇幅中再詳細介紹。

  1. 如果大家對 TypeScript 真的有興趣的話,可以移步官方文件繼續學習。
    http://www.typescriptlang.org/docs/tutorial.html
  2. 如果大家對型別系統的理論比較感興趣的,建議把 《 Types And Programming Languages 》 買了並看了。

題圖:https://unsplash.com/photos/HbbHfXvb6Xw By Nirzar Pangarkar

相關文章