請啟用 Javascript 查看內容

JavaScript 註解 & 型別檢查

 ·  ☕ 6 分鐘

JavaScript筆記 目錄

JavaScrit 註解

1. 格式

JavaScrit 中的註解有兩種格式。

單行註解:

1
// Hello

多行註解:

1
2
3
4
/*
 Hello
 Hi
*/

2. 為什麼要加註解?

除非你的記得你幾個月前寫的程式,或能立刻明白他人寫的程式,否則良好的註解是必須的。

註解功能:

  • 阻止程式碼執行。
  • 用於說明程式碼,提升可讀性,方便專案管理、交接,提升協作效率。

3. 不必要的註解

  1. 只對商業邏輯複雜的部分撰寫註解
  2. 不要在程式碼中保留被註解掉的程式碼
  3. 不要留有日誌式的註解
  4. 避免位置標明

無瑕的程式碼 JavaScript - 註解(Comments)

註解應用

1. 特殊標記註解

1
2
3
4
5
6
// TODO: 功能未完成
// FIXME: 程式碼須修復
// XXX: 實現方式待確認
// NOTE: 功能說明
// HACK: 程式碼還有優化空間
// BUG: 程式碼有 BUG

2. JSDoc

JSDoc 是最通用的 JavaScript 註解規範,透過特定格式的註解,可以快速建立 API 文件。

型別檢查

動態型別一時爽,程式碼重構火葬場

由於 Javascript 是動態型別語言,變數型別的寬容使得程式撰寫上有不錯的靈活性,但是當專案變得龐大時,反而會使開發成本提高。

使用 TypeScript 能有效解決動態型別的缺點,但考慮到學習成本與舊專案的重構,可試試 VSCode 中基於 TypeScript 提供對於 JSDoc 支持,實現智能型別檢查。

1. 開啟型別檢查

最簡單的方法就是在 .js 檔案開頭新增 @ts-check

1
2
// @ts-check

那麼 VSCode 就會根據註解檢查型別。

如果不想每隻 .js 檔案都加上 @ts-check,可以開啟 VSCode 全域型別檢查:

1
2
3
{
  "js/ts.implicitProjectConfig.checkJs": true
}

預設的情況下是關閉的:

如果型別檢查為開啟,則可以使用 @ts-nocheck 忽略某隻檔案的類型檢查:

1
2
// @ts-nocheck

2. jsconfig.json

除了啟用 VSCode 的設定來開啟型別檢查,也可以在專案根目錄下新增 jsconfig.json 設定檔:

1
2
3
4
5
{
  "compilerOptions": {
    "checkJs": true
  }
}

jsconfig.json 會覆蓋 Implicit Project Config: Check JS 設定。

預設會全域檢查所有 .js 檔案,可以使用 excludeinclude 選項,設定要排除或包含的資料夾:

1
2
3
4
5
6
7
{
  "compilerOptions": {
    "checkJs": true
  },
  "exclude": ["node_modules", "dist"],
  "include": ["src/**/*"]
}

Visual Studio Code - jsconfig.json
TypeScript - What is a tsconfig.json

3. 支持的 JSDoc 標籤

TypeScript 只支持了部分 JSDoc 標籤。

當前所支持的標籤如下:

  • @type
  • @param (or @arg or @argument)
  • @returns (or @return)
  • @typedef
  • @callback
  • @template
  • @class (or @constructor)
  • @this
  • @extends (or @augments)
  • @enum

JSDoc Reference
中文翻譯

3. 簡單範例

JSDoc 註解格式必須以 /** 為開頭才能被識別(例如 /*/*** 不會被解析為 JSDoc 註解):

1
/** */

舉例來說,我們定義一個 count 變數:

1
let count;

我們可以指定任何型別的值給它,但如果希望它只接受數字型別,可以加上 JSDoc 註解:

1
2
/** @type {number} */
let count;

若指定非數字型別的值,VSCode 將會跳出錯誤提示:

可以在註解第一行加上說明:

1
2
3
4
5
/**
 * 計數變數
 * @type {number}
 * */
let count;

如果想要忽略類型錯誤可以使用 @ts-ignore

JSDoc 標籤

1. @type

@type 可以用來標明變數型別。

基本用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/** @type {string} */
let str;
str = 'Hello!';

/** @type {Date} */
let now;
now = new Date();

/** @type {HTMLElement} */
let dom;
dom = document.querySelector('body');

複合型別:

1
2
3
4
/** @type {string|boolean} */
let x;
x = '123';
x = true;

指定陣列元素的型別:

1
2
3
/** @type {number[]} */
const ns = [];
[].push(1);

也可以寫成 Array.<number>Array<number>

1
2
3
4
5
/** @type {Array.<number>} */
const ns = [];

/** @type {Array<number>} */
const ns2 = [];

物件字面值:

1
2
3
4
5
/**
 * @type {{ a: string, b: number }}
 */
let obj;
obj = { a: '123', b: 123 };

指定 map-likearray-like 的物件:

1
2
3
4
5
6
7
/**
 * @type {Object.<string, number>}
 */
let stringToNumber;

/** @type {Object.<number, object>} */
let arrayLike;

預設就是 any 任意型別:

1
2
/** @type {any} */
let x;

*? 等同 any

1
2
3
4
5
/** @type {*} - can be 'any' type*/
let y;

/** @type {?} - unknown type*/
let z;

函式型別:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * @type {function(number): number} Closure syntax
 */
let foo;
foo = function (a) {
  return a * a;
};

/** 
 * @type {(a: number) => number} Typescript syntax
 */
let boo;
boo = function (a) {
  return a * a;
};

2. @param(synonyms: @arg or @argument)

@param 語法和 @type 基本上相同,但用於標明函式參數,所以多了參數名稱。

1
2
3
4
5
6
7
8
/**
 * The square of a number
 * @param {number} number - 輸入數字
 * @return {number}
 */
function square(number) {
  return number * number;
}

函式如果有回傳值,則可以使用 @returns@return) 標明。

有屬性的參數,使用物件字面值不易描述屬性:

1
2
3
4
5
6
/**
 * @param {{ name: string, age: number }} person
 */
function foo(person) {
  console.log(person.name, person.age);
}

可以使用以下寫法:

1
2
3
4
5
6
7
8
/**
 * @param {Object} person - 某人
 * @param {string} person.name - 某人的名字
 * @param {number} person.age - 某人的年齡
 */
function foo(person) {
  console.log(person.name, person.age);
}

使用 ES6 參數解構,使用適當的參數名稱即可:

1
2
3
4
5
6
7
8
/**
 * @param {Object} person - 某人
 * @param {string} person.name - 名字
 * @param {number} person.age - 年齡
 */
function foo({ name, age }) {
  console.log(name, age);
}

可選參數表示方式:

1
2
3
4
5
6
/**
 * @param {string=} p1 - 可選參數(Closure語法)
 * @param {string} [p2] - 可選參數(JSDoc語法)
 * @param {string} [p3 = 'test'] - 有預設值的可選參數(JSDoc語法)
 */
function foo(p1, p2, p3 = 'test') {}

3. @typedef

@typedef 可以用來宣告複雜型別,也就是自訂義一個類型,再使用 @type 標記來引用。

描述一個物件型別:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * @typedef {Object} SpecialType - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property
 * @property {number} prop2 - a number property
 * @prop {number} [prop3] - an optional number property of SpecialType
 */

/** @type {SpecialType} */
let obj;
obj = { prop1: '123', prop2: 123 };

4. @callback

@callback@typedef 相似,但描述的是一個函式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * @callback Predicate
 * @param {string} data
 * @returns {boolean}
 */

/** @type {Predicate} */
const foo = function (str) {
  return !(str.length % 2);
};

5. @class(synonyms: @constructor)

@class 可以標明函式為一個建構函式(Constructor)

1
2
3
4
5
6
7
/**
 * Creates a new Person.
 * @class
 */
function Person() {}

const p = new Person();

不過 ES6 有了 class 後,就沒必要使用 @class 了。

1
2
3
4
5
6
class Person {
  /**
   * Creates a new Person.
   */
  constructor() {}
}

6. @this

@this 可以明確標示 this 關鍵字在這裡指的是什麼。

例如建構函式的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * @class
 */
function Person() {
  this.name = '';
}

/**
 * @this {Person} - Person 實體
 */
Person.prototype.setName = function (name) {
  this.name = name;
};

或監聽器處理函式:

1
2
3
4
5
6
7
/**
 * @param {Event} event - 事件物件
 * @this {HTMLElement} - 監聽器綁定元素
 */
function clickHandler(event) {
  // ...
}

7. @extends(synonyms: @augments)

如果使用 extends 關鍵字來擴展一個現有的類別的時候,可以使用 @extends 標示。

1
2
3
4
5
6
7
8
/** 佇列 */
class Queue {}

/**
 * 優先佇列
 * @extends Queue
 */
class PriorityQueue extends Queue {}

8. @enum

@enum 標籤描述一個靜態屬性值的全部相同的集合,簡單來說就是一個物件內的屬性皆為相同型別,且不允許新增額外屬性。

1
2
3
4
5
6
/** @enum {number} */
const JSDocState = {
  BeginningOfLine: 0,
  SawAsterisk: 1,
  SavingComments: 2,
};

9. @template

@templete 非 JSDoc 標準,只在 google closure compiler 中有提及,可以用來宣告 泛用型別(Generic Type),是 TypeScript 中的型別。

泛用型別(Generic Type) 目的在於成員之間提供有意義的約束,這些成員可以是類別的實體、類別的方法、函式參數、函式回傳值。

1
2
3
4
5
6
7
8
/**
 * @template T
 * @param {T} x
 * @return {T}
 */
function foo(x) {
  return x;
}

關於 泛用型別(Generic Type) 我自己也不是很了解,有空在補充。

註解相關 VSCode 套件

  • koroFileHeader,在 VSCode 中用於生成檔案頭部註解和函式註解的套件。
    • 文件頭部新增註解:Ctrl + Alt + t
    • 光標處添加函式註解:Ctrl + Alt + t
  • Todo Tree,特殊註解高光亮。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 配置
"todo-tree.general.tags": ["TODO:", "FIXME:"],
"todo-tree.highlights.defaultHighlight": {
  "gutterIcon": true,
  "foreground": "#fff",
  "type": "text",
  "opacity": 50
},
"todo-tree.highlights.customHighlight": {
  "TODO:": {
    "background": "#ffbd2a",
    "iconColour": "#ffbd2a"
  },
  "FIXME:": {
    "background": "#f06292",
    "iconColour": "#f06292",
    "icon": "flame"
  }
}

參考文獻


竹白
作者
竹白
前端筆記

文章目錄