本文首发「码上花甲」公众号,同时发布到掘金,欢迎关注。

近期社区中不少工具放弃了 TypeScript 转而用上了 JsDoc。

咱不评价这种做法是否值得推荐,但是,有一些项目从一开始就没有使用 TypeScript。这种情况下直接迁移到 TypeScript 成本会很高,那能否用 JsDoc 来“代替” TypeScript 呢?

答案是可行的。

我们不能因为 TS 而一定要用 TS,必须要结合项目实际情况来确定。

不过 JsDoc 也有很多细节,是你可能没注意到的。除了静态类型检查之外,JsDoc 的能力还是蛮强大的。

今天,我们就结合 TS 来看下 JsDoc 都能干哪些事情。

1. 变量的类型

在 TS 中,要给一个变量定义类型很简单:

let name: string = 'Randal';

使用 TS,直接在变量 name 后面定义为 string 类型即可。

JsDoc 也能做到:

/** @type {string} */
let name = 'Randal';

编辑器同样可以识别出 JsDoc 注释的变量类型:

像其他基本类型,JsDoc 也都支持,比如 number 类型。

/** @type {number} */
let age = 30;

除了上面这种简单的基本类型之外,JsDoc 还支持联合类型。在 TS 中的写法是这样的:

const personName: 'Randal' | 'Olive' | 'Jack' = 'Randal';

JsDoc 的写法稍微麻烦一点,但也能很好的支持:

/**
 * @typedef {'Randal' | 'Olive' | 'Jack'} PersonName
 */

/**
 * @type {PersonName}
 */
const personName = 'Randal';

同样的,编辑器也能正确识别出对应的类型:

对于普通的变量,在 JsDoc 中可以用 @type 标签描述变量的类型;对于联合类型可借助 @typeof 标签辅助完成。其他的基本类型写法都差不多,就不一一展示了。

2. 函数参数和返回值

函数是 JS 中的一等公民,其重要性不言而喻。我们常见的类型定义一般都见于函数声明。

要使用 JsDoc 给函数参数定义类型,需要用到 param 标签。

比如下面的函数,用 TS 表示是这样的:

// Say hi to someone.
function sayHiSomeone(name: string) {
  console.log('Hi ' + name);
}

很简单,写法类似于给变量定义类型,直接在函数参数后面给定相应的类型即可,上面的是 string 类型。

JsDoc 的话不难,只不过需要稍微多写一点注释:

/**
 * Say hi to someone.
 * @param {string} name - the name of somebody(with type and description)
 */
function sayHiSomeone(name) {
  console.log('Hi ' + name);
}

@param 标签就表示给函数的参数定义类型和描述,{} 中的就是参数 name 的类型,后面的字符串是该参数的描述信息。

鼠标悬停在函数上,会显示函数的类型定义:

而函数的返回值被隐式地定义为了 void 类型。

也对,函数体内确实没有 return 数据出去,说明推断的是正确的。

如果要显式地定义函数返回值该怎么做呢?

/**
 * Say hi to someone.
 * @param {string} name - the name of somebody(with type and description)
 * @return {undefined}
 */
function sayHiSomeone(name) {
  console.log('Hi ' + name);
  return undefined;
}

可以用 @return 标签表示函数的返回值,{} 中定义函数返回值类型:

上面演示的都是简单类型的定义,对于稍微复杂一点的——比如对象类型或数组类型该怎么办呢?

下面是一个 TS 示例:

interface EmployeeType {
  name: string;
  department: string;
}

function Project() {}
Project.prototype.assignment = function(employee: EmployeeType) {
  console.log(employee.name + ' works in ' + employee.department);
};

借助 TS 的 interface 很容易就能定义参数 employee 的类型,在 JsDoc 中的写法却有点不一样:

function Project() {}

/**
 * 记录参数属性
 * @param {Object} employee - the employee for assignment
 * @param {string} employee.name - the name of employee
 * @param {string} employee.department - the department of employee
 */
Project.prototype.assignment = function(employee) {
  console.log(employee.name + ' works in ' + employee.department);
};

可以看到第一个 @param 标签的类型是 Object,表示 employee 参数是一个对象类型。

然后,再以此定义这个对象类型中成员的类型,第二个和第三个 @param 标签定义的都是 string 类型。

不过,这里要注意的一点是,@param 标签类型后的 employee 必须要保持和函数参数名一致,编辑器才能识别出来。

请输入图片描述

数组参数类型的写法是差不多的,比如:

/**
 * 记录数组中值的属性
 * @param {Object[]} employees - the employees params
 * @param {string} employees[].name - the name of employee
 * @param {string} employees[].department - the department of employee
 */
Project.prototype.employees = function(employees) {
  console.log(employees[0].name + ' works in ' + employees[0].department);
};

还有一种常见的场景,JsDoc 如何表示函数的参数是可选的呢?JsDoc 也考虑到这种情况了,用 [] 将参数名包裹起来就表示该参数是可选的:

/** 
 * 可选参数
 * @param {string} [somebody] - somebody's name
 */
function sayHello(somebody) {
  if (!somebody) {
    somebody = 'Randal Wang';
  }
  console.log('Hello ' + (somebody || 'me'));
}

如果参数 somebody 没有定义,那么其类型应该是 undefined

最后,一种常见的场景式函数参数是一个回调函数。回调函数在函数体内被调用:

/**
 * 回调函数
 * @param {requestCallback} cb - the callback that handles the response
 */
function callback(cb) {
  if (typeof cb === 'function') {
    cb();
  }
}

鼠标悬浮在函数上的效果如下:

3. async

async 函数表示该函数是异步的,比如在一个函数体内返回 Promise。用 JsDoc 来描述也不难:

/**
 * Download data from specitic url
 * @async
 * @param {string} url - the url to download data
 * @return {Promise<string>} the data from url
 */
async function downloadData(url) {
  return new Promise((resolve, _reject) => {
    setTimeout(() => {
      resolve(`Data from ${url}`);
    }, 1000);
  });
}

效果如下:

4. JsDoc 其他标签

除了上面提到的这几个标签较为常用之外,JsDoc 还提供了很多丰富、使用的标签,比如 @see@link@abstract@description@todo 等等。

虽然较之 TS 的能力还有不少差距,但是也已经能覆盖大部分场景了。

JsDoc 最大的特点是不会侵入到业务代码中,它是以正常的注释的形式存在的。要知道,我们即便用 TS 也是要写注释的。

所以,如果你的项目本来就是用 JS 写的,不妨先把 JsDoc 用起来。将来有机会的时候,再将其迁移到 TS 不失为一个好的选择。

上面仅仅提供几个代码片段,作为抛砖引玉吧算是。如果你对 JsDoc 其他标签还不熟悉,可以去看看官网的示例,很全面。

也可以参考和今天内容配套的源码,有一些常用标签的代码示例供你参考。

好了,今天的内容就先到这里吧,感谢阅读,下期再见 ;-)


为便于理解,为大家准备了源码以供参考:

$ npm install -g @agelesscoding/cli # 安装公众号配套的 agc 脚手架
$ agc init # 在列表中,选择对应的源码示例,比如 20231001 表示 2023 年 10 月 01 号的文章源码
Last modification:August 15, 2024
如果觉得我的文章对你有用,请随意赞赏