使用 JSDoc 標註型別

eczn發表於2018-06-29

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:

  1. xy 的型別都是 T,說明它們的型別是一樣的,返回值也一樣。
  2. 若沒有 @template 來宣告 T,那 VSCode 會認為它是建構函式 T 的型別,此處的 @template 宣告是說明 T 是“泛型”,它是一種特殊的變數,只用於表示型別而不是值

在使用了 @template 之後:

使用 JSDoc 標註型別
使用 JSDoc 標註型別

這說明,d 和 z 的型別需要通過 VSCode 理解了 addJSDoc 之後才能推匯出來,而 T 似乎是一個 變數,在上面兩種情況下分別為 stringnumber

.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 檔案並讀取,得到型別資訊:

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

相關文章