JavaScript 採用動態型別,在編譯期不對型別進行檢查,等真正執行的時候由執行時來判斷型別是否出現錯誤,這種特性有點像彙編裡的計算一樣,型別很弱,不同型別總是顯式、隱式地轉換。
在一個普通的 Todo Demo 裡型別或許沒有那麼重要,而當專案變得龐大起來的時候,裡邊有很多不同的邏輯,當專案由另外一個人接手的時候,閱讀就很麻煩,因為總是要檢視程式碼上下文的各個變數的型別(自定義型別或者原生型別),一般來說,在前端 console.log 一下就可以看到了,但也只是一個臨時的辦法,治標不治本。
當然,強型別的 JavaScript 已經很成熟了,那就是 TypeScript,但並不是所有專案一開始就是 TS 寫的,也有純 JS 的專案,那麼在這些專案裡如何維護好型別?
答案是在 VSCode 下使用 JSDoc;VSCode 會盡最大努力推匯出型別出來的。
什麼是 JSDoc
JSDoc 其實就是程式碼註釋,在這種註釋裡可以標明 JavaScript 裡值的型別,以及對程式碼的註釋,通常可以在程式碼裡看到以 @
符開頭的標註,那就是 JSDoc 了。以下是一個簡單的例子
/**
* 加法
* @param { Number } x
* @param { Number } y
* @returns { Number }
*/
function add(x, y) {
return x + y;
}
複製程式碼
上面 @param
, @returns
就說明了引數 x y 以及函式的返回值的型別。
參考 VSCode - type-checking 可以顯式的開啟 JS 的型別檢查:
開啟型別檢查可以看到傳入其他型別的引數的時候會報錯,而填入了正確的型別的值之後,VSCode 就能知道 z
是什麼型別的了(因為 x, y 均為數字,x + y 也理所當然的是數字)
也就能實現程式碼補全提示了:
標註程式碼裡的基本型別
@param
後可以接 String
Number
Array
Object
等等。 如:
/**
*
* @param { Array<Number> } numbers
* @param { String[] } names
*/
function test(numbers, names) {
names.forEach(name => {
// ...
})
}
複製程式碼
上面的 String[]
的寫法其實與 Array<String>
是一樣的,表示了一個元素為字串的陣列,而且 VSCode 同樣能程式碼自動補全 forEach
方法以及 name
的方法來(String.pototype)。
而下面的例子表示一個自定義的物件:
/**
* say hello
* @param { { name: String } } person
*/
function sayHello(person) {
console.log(person.name);
}
複製程式碼
@param
標明函式的引數的型別;@returns
標明函式的型別。
標註自定義型別
其實在上面的例子裡就有了關於自定義型別的說明(物件的那個),但上述的那個例子是針對字面量的宣告,現在介紹一下 VSCode 對 JavaScript 類的處理。
class Person {
/**
* Person
* @param { String } name
*/
constructor(name) {
this.name = name;
}
/**
* @returns { Person }
*/
sayHello() {
console.log('Hello, i am', this.name);
return this;
}
/**
* @param { Person } friend
* @returns { Person }
*/
playWith(friend) {
this.sayHello();
console.log('and i will play with', friend.name);
return this;
}
}
複製程式碼
在這裡宣告瞭 Person
類,上面有方法 sayHello
還有一個 name
屬性。在 VSCode 看來,這也是一種型別,可以作為 @param
的引數使用(上例的 playWith
函式)
順便一提,JSDoc 裡的型別標註的語法主要來自於 Google Clojure Compile 裡的型別表示式。
泛型(有限制的)
JSDoc 同樣提供了一定能力的 泛型
,但這種泛型限制很大,跟真正的泛型差的比較遠。
回到剛剛的例子 add(x, y)
如果 x y 限定死了數字,那字串型別的 x y 的相加是否還需要重新編寫一次 add 呢?其實不用,可以使用 @template
的方式來完成。
/**
* 加法
* @template T
* @param { T } x
* @param { T } y
* @returns { T }
*/
function add(x, y) {
return x + y;
}
複製程式碼
此處引入了 T
,可以這樣理解這段 JSDoc:
x
和y
的型別都是T
,說明它們的型別是一樣的,返回值也一樣。- 若沒有
@template
來宣告 T,那 VSCode 會認為它是建構函式 T 的型別,此處的 @template 宣告是說明 T 是“泛型”,它是一種特殊的變數,只用於表示型別而不是值
在使用了 @template 之後:
這說明,d 和 z 的型別需要通過 VSCode
理解了 add
的 JSDoc
之後才能推匯出來,而 T
似乎是一個 變數
,在上面兩種情況下分別為 string
和 number
。
.d.ts
還有一種型別標註的方法即使用 *.d.ts
檔案,通常普通的 JavsScript 庫是不帶型別標註的,這樣會不相容 TypeScript 裡的型別檢查,因此需要引入 d.ts
用於描述 js 庫的型別讓 ts 能夠使用。
當然,VSCode 可以讀取它,並方便開發。編寫 test.js:
/**
* @param { Person } p
*/
function test(p) {
}
複製程式碼
然後編寫 Person 的型別描述 test.d.ts
declare interface Person {
name: string,
age?: number,
sayHello: () => Person,
playWith: (friend: Person) => Person
}
複製程式碼
注:age 的問號代表年齡可有可無。
然後 VSCode 會找到 *.d.ts 檔案並讀取,得到型別資訊:
參考連結
http://www.css88.com/doc/jsdoc/about-block-inline-tags.html https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler#type-expressions https://code.visualstudio.com/Docs/languages/javascript#_type-checking https://zh.wikipedia.org/wiki/JavaScript