void 运算符在 ES6 的运用
void
操作符在过去有以下几个使用用例:
- 与
<a href="javascript:void(0)">
一起使用来实现动态按钮, 但这是一种糟糕且过时的做法 - 在 ES5 之前的年代标识符
undefined
随时可能被覆盖的, 它可以用来访问undefined
原始值 - 将
undefined
缩写为void 0
可以省去一些字符, 即使是现在这种缩写依然有人在使用
这些情况都都是在 ES3 中常用, 与现在常用的 ES6 没有特别的关系, 除了在 ES6 的箭头函数中带来了一些新的功能:
使用 void
解释没有返回值的函数
ES6 得箭头函数表达式为了代码更简洁允许省略函数体中的花括号。但这种简写会产生一种模糊性, 导致我们无法判断某个函数是否有返回值, 因为这些函数也有可能只是起到其它作用(如, console.log 只是用来打印 log, 调用后不会有返回值), 而它又没有返回值:
// 省略花括号
const id = x => x;
// 调用没有返回值的函数
const log = x => console.log(x);
console.log
是大家熟知的, 因此这个示例并不是模棱两可, 但是它说明了原理。
当然, 我们也可以有意地通过添加花括号来使箭头函数地函数性质更明确, 但这就失去了箭头函数该有地简洁:
const log = x => {
console.log(x);
}
上面的函数这时候就可以借助 void
显示在一行, 相比像 Prettier 这类自动化格式工具通过添加换行符来显示函数没有返回值, 这种方式更显得明显(如果你有过其它语言学习的经验地话)也简洁:
const log = x => void console.log(x);
像这样使用 void
的方式最明显的好处是减少了行数。尤其是像经常依赖 void 回调函数的 CPS 函数式编程 (continuation-passing style)。
类型系统 and void 类型
在许多语言中的类型系统,包括 C/C++, TypeScript 的类型系统, 都具有 void 类型概念, 这使 void
运算符可用于更好地补充和认识那些没有返回值的函数。
React 中的 useEffect
hook
void
的常见实用案例其实是在 React 中的 useEffect
hook, 例如:
useEffect(() => void (document.title = 'example'));
这是一个很明显的示例,因为 useEffect
可以使用其回调函数的返回值来移除效果,因此如果回调的函数返回的是函数就可能在运行时出现 bug。
同样的情况是 Immer 的 produce()
函数:
produce(draft => void (draft.user.age += 1))
在 async IIFES 中使用 void
对于异步立即调用函数表达式(async immediately-invoked function expressions, 简写 async IIFES)的 void
运算符, 还有另外一个不太常见的用例。以往, IIFES 被用于实现模块作用域, 因为古旧的 JS (ES3)没有块作用域或实际模块, 但是现在的 JS (像ES5, ES6 之后的标准)却同时具有两者, 所以 IIFES 除了以下一种情况之外基本上不相关: 在模块的顶层上使用 theawait
或 for-await-of
语法, 例如:
void (async () => {
await fetch(something);
})();
这是个常见的用例, 而 ECMAScript 的 stage 3 proposal 就有相关探讨。现阶段我们仍需在函数表达式周围加上括号, 但是使用 void
解决了前括号不能与 ASI 一起使用的问题(更具体地说, 它在没有显式分号行终止符的情况下会中断)。
代码风格和 no-void
流行的代码风格指南(如 Airbnb 和 Standard JS)使用 no-void
ESLint 规则来禁止编程人员使用 void
运算符, 然而, 这种规定充满了争议, 它应该运行用户去重写或覆盖。
相关资料链接:
ECMAScript proposal: top-level await
Immer doc: Inline shortcuts using void
ESLint issue#'no-void' should have an option to allow void arrow functions or void async IIFEs