強型別 JavaScript 的解決方案

阮一峰發表於2015-02-08

JavaScript 是一種弱型別(或稱動態型別)語言,即變數的型別是不確定的。


x = 5; // 5
x = x + 'A'; // '5A'

上面程式碼中,變數x起先是一個數值,後來是一個字串,型別完全由當前的值決定,這就叫弱型別。

弱型別的好處是十分靈活,可以寫出非常簡潔的程式碼。但是,對於大型專案來說,強型別更有利,可以降低系統的複雜度,在編譯時就發現型別錯誤,減輕程式設計師的負擔。

一直有人嘗試,讓 JavaScript 變成強型別語言。在官方最終支援強型別之前,本文介紹三種現在就可用的解決方案。

(題圖:攝於花蓮,臺灣,2012年6月)

一、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》

本文的原始幻燈片點選這裡(裡面有更多內容)。

(完)

相關文章