工做了四年多,基本上都在围绕着 JavaScript 作事情。
写的代码多了,看的代码也多了,由衷的以为,写出别人看不懂的代码并非什么能力,写出全部人都能读懂的代码,才是真的牛X。
众所周知, JavaScript 是一个弱类型的脚本语言,这就意味着,从编辑器中并不能直观的看出这段代码的做用是什么,有些事情只有等到代码真正的运行起来才可以肯定。
因此为了解决大型项目中 JavaScript 维护成本高的问题,前段时间咱们团队开始使用 TypeScript,可是由前几年所积累下来的代码,并非说改立马都能所有改完的,因此这个重构将是一个漫长的过程。
在重构同时咱们仍是须要继续维护原有的 JavaScript 项目的,而 JSDoc 刚好是一个中间过渡的方案,可让咱们以注释的形式来下降 JavaScript 项目的维护难度,提高可读性。javascript
本人使用的是 vs code 编辑器,内置了对 jsdoc 的各类支持,同时还会根据部分常量,语法来推测出对应的类型
能够很方便的在编辑器中看到效果,因此下面全部示例都是基于 vscode 来作的。html
首先,JSDoc 并不会对源码产生任何的影响,全部的内容都是写在注释里边的。
因此并不须要担忧 JSDoc 会对你的程序形成什么负面影响。java
能够先来看一个普通的 JavaScript 文件在编辑器中的展现效果:
异步
很显而易见的,编辑器也不可以肯定这个函数到底是什么含义,由于任何类型的两个参数均可以进行相加。
因此编辑器就会使用一个在 TypeScript 中常常出现用来标识任意类型的 any
关键字来描述函数的参数以及返回值。async
而这种状况下咱们能够很简单的使用 JSDoc 来手动描述这个函数的做用: 编辑器
实际上有些函数是须要手动指定
@return {TYPE}
来肯定函数返回值类型的,但由于咱们函数的做用就是经过两个参数相加并返回,因此编辑器推算出了函数返回值的类型。函数
对比上下两段代码,代码上并无什么区别,也许有人会嗤之以鼻,认为代码已经足够清晰,并不须要额外的添加注释来讲明。
这种盲目自信通常会在接手了其余人更烂的代码后被打破,而后再反思本身究竟作错了什么,须要去维护这样的代码。ui
亦或者咱们来放出一个稍微复杂一些的例子:spa
看似清晰、简洁的一个示例,彻底看不出什么毛病 除了两个异步await
能够合并成一个。
确实,若是这段代码就这么一直躺在项目中,也不去改需求,那么这段代码能够说是很完美的存在了。
若是这段代码一直是写下这段代码的做者在维护,那么这段代码在维护上也不会有什么风险。3d
不过若是哪天这段代码被交接了出去,换其余的小伙伴来维护。
那么他可能会有这么几个疑问:
getUserInfo
的返回值是什么结构createOrder
的返回值又是什么结构notify
中传入的两个变量又都是用来作什么的咱们也只可以从notify
函数中找到一些线索,查看到前两个函数所返回对象的部分属性, 可是仍然不能知道这些属性的类型是什么。
而想要维护这样的一段代码,就须要占用不少脑容量去记忆,这其实是一个性价比很是低的事情,当这段代码再转给第三我的时,第三我的还须要再经历完整的流程,一个个函数、一行行代码去阅读,去记忆。
若是你把这个看成是对程序的深刻了解程度、对业务的娴熟掌握,那么我以为我也帮不了你了。 就像是如今超市结帐时,没有柜员会以可以记忆N多商品价格而感到骄傲,扫码枪能作到的事情,为何要占用你的大脑呢。
如上文所说的,JSDoc 是写在注释中的一些特定格式内容。
在 JavaScript 文件中大部分的标记都是块级形式的,也就是使用 /** XXX */
来进行定义,不过若是你愿意的话,也能够写到代码里边去。
JSDoc 提供了不少种标记,用于各类场景。
但并非全部的都是经常使用的(并且使用了 vscode 之后,不少须要手动指定的标记,编辑器都可以代替你完成),经常使用的无外乎如下几个:
完整的列表能够在这里找到 Block tags
基本上使用以上三种标记之后,已经可以解决绝大部分的问题。
JSDoc 在写法上有着特定的要求,好比说行内也必需要是这样的结构 /** XXX */
,若是是 /* XXX */
则会被忽略。
而多行的写法是比较经常使用的,在 vscode 中能够直接在函数上方键入 /**
而后回车,编辑器会自动填充不少的内容,包括参数类型、参数描述以及函数描述的预留位置,使用TAB
键便可快速切换。
实际上@type
的使用频率相较于其余两个是很低的,由于大多数状况下@type
用于标识变量的类型。
而变量的来源基本上只有两个 1. 基本类型赋值 2. 函数返回值 首先是第一个基本类型的赋值,这个基本上 vscode 就帮你作了,而不须要本身手动的去指定。
而另一个函数的返回值,若是咱们在函数上添加了@return
后,那么调用该函数并获取返回值的变量类型也会被设置为@return
对应的类型。
不过由于其余两个标记中都有类型相关的指定,因此就拿 @type 来讲明一下
首先,在 JSDoc 中是支持全部的基本类型的,包括数字、字符串、布尔值之类的。
/** @type {number} */
/** @type {string} */
/** @type {boolean} */
/** @type {RegExp} */
// 或者是一个函数
/** @type {function} */
// 一个包含参数的函数
/** @type {function(number, string)} */
// Object结构的参数
/** @type {function({ arg1: number, arg2: string })} */
// 一个包涵参数和返回值的函数
/** @type {function(number, string): boolean} */
复制代码
在 vscode 中键入以上的注释,均可以很方便的获得动态提示。
固然了,关于函数的,仍是推荐使用 @param 和 @return 来实现,效果更好一些
上边的示例大可能是基于基本类型的描述,但实际开发过程当中不会说只有这么些基本类型供你使用的。
必然会存在着大量的复杂结构类型的变量、参数或返回值。
关于函数参数,在 JSDoc 中两种方式能够描述复杂类型:
不过这个只能应用在@param
中,并且复用性并不高,若是有好几处一样结构的定义,那咱们就须要把这样的注释拷贝多份,显然不是一个优雅的写法。
又或者咱们可使用另外两个标记,@typedef
和@property
,格式都与上边提到的标记相似,能够应用在全部须要指定类型的地方:
使用@typedef
定义的类型能够很轻松的复用,在须要的地方直接指定咱们定义好的类型便可。
同理,这样的自定义类型能够直接应用在@return
中。
这个算是比较重要的一个标记了,用来标记函数参数的相关信息。
具体的格式是这样的(切换到 TypeScript 后通常会移除类型的定义,改用代码中的类型定义):
/** * @param {number} param 描述 */
function test (param) { }
// 或者能够结合着 @type 来写(虽然说不多会这么写)
/** * @param param 描述 */
function test (/** @type number */ param) { }
复制代码
若是咱们想要表示一个参数为可选的参数,能够的在参数名上包一个[]
便可。
/** * @param {number} [param] 描述 */
function test (param) { }
复制代码
同事在文档中还提到了关于默认值的写法,实际上若是你的可选参数在参数位已经有了默认值的处理,那么就再也不须要额外的添加[]
来表示了,vscode 会帮助你标记。
// 文档中提到的默认值写法
/** * @param {number} [param=123] 描述 */
function test (param = 123) { }
// 而实际上使用 vscode 之后就能够简化为
/** * @param param 描述 */
function test (param = 123) { }
复制代码
二者效果是同样的,而且因为咱们手动指定了一个基础类型的值,那么咱们连类型的指定均可以省去了,简单的定义一下参数的描述便可。
该标记就是用来指定函数的返回值,用法与@param
类型,而且基本上这两个都会同时出现,与@param
的区别在于,由于@return
只会有一个,因此不会像前者同样还须要指定参数名。
/** * @return {number} 描述 */
function test () { }
复制代码
如今这个年代,基本上Promise
已经普及开来,因此不少函数的返回值可能并非结果,而是一个Promise
。
因此在vscode中,基于Promise
去使用@return
,有两种写法可使用:
// 函数返回 Promise 实例的状况能够这么指定类型
/** * @return {Promise<number>} */
function test () {
return new Promise((res) => {
res(1)
})
}
// 或者使用 async 函数定义的状况下能够省略 @return 的声明
async function test () {
return 1
}
// 若是返回值是一个其余定义了类型的函数 or 变量,那么效果同样
async function test () {
return returnVal()
}
/** @return {string} */
function returnVal () {}
复制代码
再回到咱们最初的那个代码片断上,将其修改成添加了 JSDoc 版本的样子:
/** * @typedef {Object} UserInfo * @property {number} uid 用户UID * @property {string} name 昵称 * * @typedef {Object} Order * @property {number} orderId 订单ID * @property {number} price 订单价格 */
async function main () {
const uid = 1
const orders = await createOrder(uid)
const userInfo = await getUserInfo(uid)
await notify(userInfo, orders)
}
/** * 获取用户信息 * @param {number} uid 用户UID * @return {Promise<UserInfo>} */
async function getUserInfo (uid) { }
/** * 建立订单 * @param {number} uid 用户UID * @return {Promise<Order>} */
async function createOrder (uid) { }
/** * 发送通知 * @param {UserInfo} userInfo * @param {Order} orders */
async function notify (userInfo, orders) { }
复制代码
实际上并无添加几行文本,在切换到 TypeScript 以前,使用 JSDoc 可以在必定程度上下降维护成本,尤为是使用 vscode 之后,要手动编写的注释其实是没有多少的。
可是带来的好处就是,维护者可以很清晰的看出函数的做用,变量的类型。代码即文档。
而且在进行平常开发时,结合编辑器的自动补全、动态提示功能,想必必定是可以提升开发体验的。
上边介绍的只是 JSDoc 经常使用的几个标记,实际上还有更多的功能没有提到,具体的文档地址:jsdoc