JsDoc vs TypeScript

本文首发微信公众号「码上花甲」

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

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

答案是可行的。

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

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

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

1. 变量的类型

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

js 复制代码
let name: string = 'Randal';

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

JsDoc 也能做到:

js 复制代码
/** @type {string} */
let name = 'Randal';

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

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

js 复制代码
/** @type {number} */
let age = 30;

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

js 复制代码
const personName: 'Randal' | 'Olive' | 'Jack' = 'Randal';

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

js 复制代码
/**
 * @typedef {'Randal' | 'Olive' | 'Jack'} PersonName
 */

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

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

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

2. 函数参数和返回值

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

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

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

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

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

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

js 复制代码
/**
 * 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 数据出去,说明推断的是正确的。

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

js 复制代码
/**
 * 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 示例:

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 中的写法却有点不一样:

js 复制代码
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 必须要保持和函数参数名一致,编辑器才能识别出来。

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

js 复制代码
/**
 * 记录数组中值的属性
 * @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 也考虑到这种情况了,用 [] 将参数名包裹起来就表示该参数是可选的:

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

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

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

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

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

3. async

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

js 复制代码
/**
 * 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 其他标签还不熟悉,可以去看看官网的示例,很全面。

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

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


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

bash 复制代码
$ npm install -g agelesscoding/cli # 安装公众号配套的 agc 脚手架
$ agc init # 在列表中,选择对应的源码示例,比如 20231001 表示 2023 年 10 月 01 号的文章源码
相关推荐
Hilaku22 分钟前
我用 Gemini 3 Pro 手搓了一个并发邮件群发神器(附源码)
前端·javascript·github
IT_陈寒22 分钟前
Java性能调优实战:5个被低估却提升30%效率的JVM参数
前端·人工智能·后端
快手技术24 分钟前
AAAI 2026|全面发力!快手斩获 3 篇 Oral,12 篇论文入选!
前端·后端·算法
颜酱25 分钟前
前端算法必备:滑动窗口从入门到很熟练(最长/最短/计数三大类型)
前端·后端·算法
全栈前端老曹34 分钟前
【包管理】npm init 项目名后底层发生了什么的完整逻辑
前端·javascript·npm·node.js·json·包管理·底层原理
HHHHHY40 分钟前
mathjs简单实现一个数学计算公式及校验组件
前端·javascript·vue.js
boooooooom43 分钟前
Vue3 provide/inject 跨层级通信:最佳实践与避坑指南
前端·vue.js
一颗烂土豆43 分钟前
Vue 3 + Three.js 打造轻量级 3D 图表库 —— chart3
前端·vue.js·数据可视化
青莲84344 分钟前
Android 动画机制完整详解
android·前端·面试
iReachers1 小时前
HTML打包APK(安卓APP)中下载功能常见问题和详细介绍
前端·javascript·html·html打包apk·网页打包app·下载功能