JSDoc简单应用
JSDoc 是一个根据 JavaScript 文件中注释信息,生成 JavaScript 应用程序、库或模块的 API 文档的工具。
它的本质是代码注释,但它也有一定的格式和规则。
## JSDoc 注释
JSDoc 注释一般应该放置在方法或函数声明之前,它必须以 `/**` 开头,以便由 JSDoc 解析器识别。其他任何以 `/*`、`/***` 或者超过 3 个星号的注释,都会被 JSDoc 解析器忽略。如以下代码所示:
```js
/**
* 加法运算
*/
function plus (a, b) {}
/**
* 减法运算
*/
function minus (a, b) {}
```
## JSDoc 注释标签
在 JSDoc 注释中有一套标准的注释标签,一般以 `@` 开头。如下代码所示:
```js
/**
* 加法运算
* @param { number } a - 加数
* @param { number } b - 被加数
* @returns { number } 两数之和
*/
function plus (a, b) {}
/**
* 减法运算
* @param { number } a - 减数
* @param { number } b - 被减数
* @returns { number } 两数之差
*/
function minus (a, b) {}
```
因为 JSDoc 经历过好几个版本,为了向后兼容,所以也存在**别名**,比如 `@param` 就有以下两个别名:
+ `@arg`
+ `@argument`
在某些场景下,别名可能更加语义化。
## 案例说明
本文只会对个别常用案例中使用到的注释标签进行说明,并不会涉及到所有的 JSDoc 注释标签。
### 变量 / 常量
在 JSDoc 中,并没有变量的注释标签,它只有一个常量的注释标签 `@constant`,别名为 `@const`:
```js
@constant [ ]
```
`type` 和 `name` 都是可选的:
```js
/** @constant */
const A = 1
/** @const { string } B - 字符串 */
const B = ''
/**
* @constant 一个布尔标识
* @type { boolean }
* @default
*/
const C = false
```
`@type` 标签中提供了一个类型,这是可选的。另外,`@default` 标签在这里也是一样,这里将自动添加指定的值(如:`false`)给文档。
虽然,它是一个常量的注释标签,但也可以有效地用在 `var`、`let` 声明上:
```js
/** @constant { string } */
let a = 'string'
/** @const { number } */
var b = 1
```
#### enum
`@enum` 标签描述一个静态属性值的全部相同属性集合。枚举类似一个属性的集合,除了枚举自己描述注释之外,其它属性都记录在容器内部的注释中。
`@enum` 通常与 `@readonly` 结合使用,作为一个枚举通常表示常量的集合:
```js
/**
* @readonly
* @enum { number }
*/
const state = {
TRUE: 1,
FALSE: -1,
/** @type { boolean } */
MAYBE: true
}
```
### 函数
通过 `@function` 标签将一个对象标记为函数/方法:
```js
const arithmetic = {
plus (a, b) {
return a + b
}
}
/** @function */
const plus = arithmetic.plus
```
另外,我们可以通过 `@name` 标签来指定该函数的名称:
```js
/**
* @function
* @name plus
*/
const plus = arithmetic.plus
```
当然,这些只是针对生成文档时的应用,对于编辑器提示并没有什么卵用。
下面我们来了解一下如果对函数参数以及返回值进行注释:
```js
const arithmetic = {
/**
* 获取两数之和
* @arg { number } a - 加数
* @arg { number } b - 减数
* @returns { number } 两数之和
*/
plus (a, b) {
return a + b
}
}
```
我们可以通过 `@params` 或别名 `@arg`、`@argument` 等标签对函数的参数进行注释:
```js
@param [] name []
@arg [] name []
@argument [] name []
```
通过 `@returns` 标签对函数的返回值进行注释:
```js
@return [ ]
```
在编辑器里面,当鼠标移动到对应的方法,我们应该可以看到以下的结果:

在函数中,还存在着可选参数,如以下函数所示,性别是可选的(0 表示保密,1 表示男,2 表示女):
```js
function test (name, age, gender = 0) {}
```
我们可以将给参数名加上 `[]` 来表示该参数是可选的:
```js
/**
* Test
* @param { string } name - 名字
* @param { number } age - 年龄
* @param { number } [gender] - 性别(0 表示保密,1 表示男,2 表示女)
*/
function test (name, age, gender = 0) {}
```
另外,也可以给可选参数加上默认值:
```js
/**
* Test
* @param { string } name - 名字
* @param { number } age - 年龄
* @param { number } [gender=0] - 性别(0 表示保密,1 表示男,2 表示女)
*/
function test (name, age, gender = 0) {}
```
#### 多类型参数和可重复使用的参数
使用 `|` 可以表示一个参数允许不同的类型:
```typescript
/**
* SayHello
* @param { string | string[] } somebody - 名称,或名称组成的数组
*/
function sayHello (somebody = 'zhangsan') {
if (Array.isArray(somebody)) {
console.log(`Hello ${somebody.join(',')}`)
} else {
console.log(`Hello ${somebody}`)
}
}
```
使用 `*` 表示一个参数允许任意类型:
```js
/**
* @param { * } any - 任意类型参数
*/
function testAny (any) {}
```
使用 `...` 表示参数个数不确定,但类型一样,也就是可重复使用的参数:
```js
/**
* 求和
* @param { ...number } num - 正数或负数
*/
function sum () {
let result = 0
for (i = 0; i < arguments.length; i++) {
result += arguments[i]
}
return result
}
```
#### 回调函数
如果函数接受一个回调函数作为参数,那么我们可以使用 `@callback` 标签来定义一个回调函数,回调函数的参数类型包含到 `@param` 标签中:
```js
/**
* 这是一个名为 reqCallback 的回调函数,它接收一个字符串类型的参数:res
* @callback reqCallback
* @param { string } res - 返回结果
*/
/**
* 一个异步函数,接收一个 cb 作为参数
* @param { string } name - 用户名
* @param { reqCallback } cb - 回调函数
*/
function asyncFunc (name, cb) {
setTimeout(() => {
cb && cb(`Hello ${name}`)
}, 200)
}
```
需要注意的是,回调函数的类型声明是在另一个注释块里面。
#### 对象参数
如果对象作为参数,我们可以把类型设置成 `Object`,通过 `@param` 可以对该对象中其它参数进行注释:
```js
/**
* 对象作为参数
* @param { Object } person - 一个人
* @param { string } person.name - TA 的名字
* @param { number } person.age - TA 的年龄
* @param { number } [person.gender] - TA 的性别
*/
function objFunc (person) {
console.log(person.name)
console.log(person.age)
console.log(person.gender)
}
```

同理,如果参数是一个对象数组,那我们可以这样描述:
```js
/**
* 对象作为参数
* @param { Object[] } persons - 人的集合
* @param { string } persons[].name - TA 的名字
* @param { number } persons[].age - TA 的年龄
* @param { number } [persons[].gender] - TA 的性别
*/
function objFunc (persons) {
if (Array.isArray(persons)) {
persons.forEach(person => {
console.log(person.name)
console.log(person.age)
console.log(person.gender)
})
}
}
```
#### 函数使用示例
有些时候,我们需应该给函数添加一些使用示例:
```js
/**
* 获取参数之和
* @param { ...number } num - 正数或负数
* @return { number } 参数之和
*
* @example
* sum(1, 2)
*
* @example
* sum(-1, 2)
*/
function sum () {
let result = 0
for (i = 0; i < arguments.length; i++) {
result += arguments[i]
}
return result
}
```

### 类
`@class` 标签指明函数是一个构造函数,意味着该函数需要使用 `new` 关键字来实例化,它的别名是 `@constructor`:
```js
@class [ ]
@constructor [ ]
```
如下所示:
```js
/**
* Create a new person
* @class
*/
function Person () {}
const p = new Person()
```
#### 修饰符
在面向对象中,类的修饰符有三种:`private`、`protected` 和 `public`。同样在 JSDoc 也有相应的注释标签:`@private`、`@protected` 和 `@public`。
```js
/**
* @class
*/
class Person {
/**
* 名字
* @public
* @type { string }
*/
name
/**
* 年龄
* @protected
* @type { number }
*/
age
/**
* 性别
* @private
* @type { number }
* @default
*/
gender = 0
/**
* @param { string } name
* @param { number } age
* @param { number } [gender]
*/
constructor (name, age, gender = 0) {
this.name = name
this.age = age
this.gender = gender
}
}
```
当然,少不了 `@static`:
```js
/**
* @class
*/
class Person {
// ...
/**
* 工具函数,将字符串转成数字
* @static
* @param { string | number } str
* @returns { number }
*/
static str2number (str) {
return Number(str)
}
}
```
#### this
`@this` 标签指明 `this` 关键字的指向:
```js
/**
* @constructor
* @param { string } name
*/
function Greeter (name) {
setName.apply(this, name)
}
/**
* @this Greeter
* @param { string } name
*/
function setName (name) {
this.name = name
}
```
#### extends
使用 `@extends` 标签来扩展类:
```js
/**
* @class
* @this Person
* @param { string } name
*/
function Person (name) {
this.name = name
}
/**
* @class
* @extends Person
*/
function Male () {}
Male.prototype = Person.prototype
```
### 自定义一个类型
通过 `@typedef` 可以自定义一个类型。
```js
/**
* @typedef { Object } Person
* @property { string } name - 名字
* @property { number } age - 年龄
* @property { number } [gender] - 性别
*/
/**
* @param { Person } person
*/
function test (person) {
console.log(person.name, person.age, person.gender)
}
```

#### 接口的返回值
利用自定义类型来定义一个接口的返回值类型:
```js
/**
* @template T
* @typedef ResponseData
* @property { code } number
* @property { message } string
* @property { T } data
*/
/**
* @typedef { Object } UserInfo
* @property { string } name
* @property { number } age
* @property { number } [gender]
* @property { Skill[] } [skills]
* @property { UserInfo[] } [friends]
*/
/**
* @typedef Skill
* @type { '唱' | '跳' | 'Rap' | '篮球' }
*/
/**
* 获取用户信息
* @param { string } url
* @returns { Promise> }
*/
function getUserInfo (url) {
return axios.get(url)
}
const zhangsan = getUserInfo('https://www.baidu.com').then(res => {
console.log(res.data.skills)
})
```

这里需要注意的是,在 JSDoc 中,需要使用 `@template` 来标识一个泛型类型。
### 其他
#### @since
`@since` 表示一个功能是哪个版本被加入的:
```js
/**
* 获取随机数
* @since 1.0.0
* @return { number }
*/
function getRandom () {
return Math.random()
}
```
#### @deprecated
`@deprecated` 表示该功能已经被废弃:
```js
/**
* @deprecated since version 1.2.3
*/
function oldFunc () {}
```
#### @see
`@see` 表示可以参考另一个标识符的文档,或一个外部资源:
```js
/**
* @see {@link bar}
* @see bar
*/
function foo () {}
/**
* @see {@link foo}
* @see {@link https://www.baidu.com}
*/
function bar () {}
```
#### @todo
`@todo` 记录一个需要完成的任务:
```js
/**
* @todo Implement this function.
* @todo Write the documentation.
*/
function test (a) {
}
```