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 号的文章源码
相关推荐
HEX9CF18 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
凌云行者31 分钟前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
华农第一蒟蒻1 小时前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
积水成江1 小时前
关于Generator,async 和 await的介绍
前端·javascript·vue.js
___Dream1 小时前
【黑马软件测试三】web功能测试、抓包
前端·功能测试
金灰1 小时前
CSS3练习--电商web
前端·css·css3
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
Tandy12356_1 小时前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
TonyH20021 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包
你会发光哎u1 小时前
Webpack模式-Resolve-本地服务器
服务器·前端·webpack