JavaScript 是一種弱型別(或稱為動態型別)語言,即變數的型別是不確定的。
x = 5; // 5 x = x + 'A'; // '5A'
上面程式碼中,變數x起先是一個數值,後來是一個字串,型別完全由目前的值決定,這就叫弱型別。
弱類型的好處是十分靈活,可以寫出非常簡潔的程式碼。但是,對於大型專案來說,強類型更有利,可以降低系統的複雜度,在編譯時就發現類型錯誤,減輕程式設計師的負擔。
一直有人嘗試,讓 JavaScript 變成強型別語言。在官方最終支援強類型之前,本文介紹三種現在就可用的解決方案。
一、TypeScript
TypeScript 是微軟2012年推出的一種程式語言,屬於 JavaScript 的超集,可以編譯為 JavaScript 執行。 它的最大特點就是支援強型別和 ES6 Class。
首先,安裝TypeScript。
$ npm install -g typescript
然後,為變數指定型別。
// greet.ts function greet(person: string) { console.log("Hello, " + person); } greet([0, 1, 2]);
上面是檔案 greet.ts 的程式碼,後綴名 ts 表示這是 TypeScript 的程式碼。函數 greet 的參數,宣告類型為字串,但在呼叫時,傳入了一個陣列。
使用 tsc 指令將 ts 文件編譯為 js 文件,就會拋出類型不符的錯誤。
$ tsc greeter.ts greet.ts(5,9): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
二、Flowcheck
Flowcheck 是一個輕量級的類型斷言庫,可以在運行時(runtime)檢查變數類型是否正確。
首先,安裝Flowcheck。
$ npm install -g flowcheck
然後,寫一個宣告了變數類型的腳本。
function sum(a: number, b: number) { return a + b; } sum('hello','world')
接著,使用下面的命令,將腳本轉換為正常的 JavaScript 檔案。
$ browserify -t flowcheck -t [reactify --strip-types] \ input.js -o output.js
轉換後的文件如下。
var _f = require("flowcheck/assert"); function sum(a, b) { _f.check(arguments, _f.arguments([_f.number, _f.number])); return a + b; }
可以看到,程式碼中插入一個斷言函式庫。每次執行函數之前,會先執行斷言,如果類型不符就報錯。
$ node output.js // throw new TypeError(message); ^ TypeError: Expected an instance of number got "hello", context: arguments / [number, number] / 0 Expected an instance of number got "world", context: arguments / [number, number] / 1
三、Flow
Flow 是 Facebook 在2014年發布的一個類型檢查工具,用來檢查 React 的源碼。
安裝指令如下。
$ npm install --global flow-bin
如果安裝不成功(我就是如此),就需要自己從原始碼編譯了。
Flow 的用法很多,我只舉幾個例子。前文介紹的兩種工具,只能檢查聲明了類型的變量,而 Flow 可以推斷變量類型。
// hello.js /* @flow */ function foo(x) { return x*10; } foo("Hello, world!");
上面是檔案 hello.js ,該檔案的第一行是註釋,表示需要使用 Flow 檢查變數類型。
$ flow check hello.js:7:5,19: string This type is incompatible with /hello.js:4:10,13: number
執行 flow check 指令,得到報錯訊息:預期函數 foo 的參數是一個數值,但是實際為一個字串。
Flow 也支援變數的型別宣告。
/* @flow */ function foo(x: string, y: number): string { return x.length * y; } foo("Hello", 42);
另一個有趣的功能是,Flow 可以將類型註解(annotation),轉換為類型宣告。
// annotation.js /** @param {number} x @return {number} */ function square(x) { return x * x; } square(5);
執行 flow port 指令,會得到下面的結果。
$ flow port annotation.js function square(x: number) : number { return x * x; }
Flow 的更多介紹,可以閱讀《Exploring Flow, Facebook's Type Checker for JavaScript》。