神秘链接 javascript:void(0) 背后的故事。
2025年4月16日Elecmonkey
在上古互联网时代,大家可能都见过这么一个神秘链接:javascript:void(0)
。这个javascript:void(0)
链接曾经遍布互联网的每个角落。
今天手写代码已经很少用到这个奇怪的 JavaScript 运算符了——它执行任何表达式,但返回值总是 undefined
。不过在一些构建产物中还有它的身影,比如,写这么一个神奇的TS函数:
typescript1function test(param: any) { 2 console.log(param.data?.value); 3}
tsc
会把它编译成:
javascript1function test(param) { 2 var _a; 3 console.log((_a = param.data) === null || _a === void 0 ? void 0 : _a.value); 4}
蒽?void 0
,why not undefined
?
undefined in JavaScript 👻
void
的存在确实有那么一点道理。在 JavaScript 中,undefined
并不是一个关键字,而仅仅是全局对象的一个普通属性:
javascript1// 在 ES5 之前,这样的代码是完全合法的 2undefined = "这不是 undefined 了"; 3console.log(undefined);
ES6 之后,该属性的值被设置为只读(writable: false
),无法再被修改。不过,在局部作用域中仍然可以被覆盖:
javascript1function evil() { 2 let undefined = "我是邪恶的值"; 3 console.log(undefined); 4}
虽然应该不会有人故意把变量命名成 undefined
并重新赋值,但总之,理论上讲,使用 undefined
你不能保证你获取到的是 undefined
,反而是使用 void
运算符总能获取到真正的 undefined
。
void 运算符
So,为了解决这个问题,JavaScript 引入了 void
运算符。无论 undefined
被如何污染,void
运算符总是会返回真正的 undefined
值:
javascript1// 即使 undefined 被重新赋值 2globalThis.undefined = "被污染了"; 3// window 和 global 4 5// void 运算符仍然返回真正的 undefined 6console.log(void 0); // undefined 7console.log(void(0)); // undefined 8console.log(void "任何表达式"); // undefined
由于 void 0
是获取 undefined
最短的方式,它成为了许多 JavaScript 压缩工具的首选。
javascript:void(0) 的由来
那么,为什么在早期的网页中会经常看到 javascript:void(0)
呢?这和早期浏览器 HTML 中 <a>
标签的行为有关。
当用户点击一个javascript:
协议的链接时,浏览器会:
- 执行
href
属性中的 JavaScript 代码 - 使用返回值更新当前页面(如果有返回值的话)
例如:
html1<!-- 这会导致页面被替换为 "1" --> 2<a href="javascript:1">点击我</a> 3 4<!-- 这什么都不会发生 --> 5<a href="javascript:void(0)">点击我</a>
(大家不用自己去试了,现代浏览器除非你 document.write
,否则不会重写页面内容的)
因此,javascript:void(0)
成为了阻止链接默认行为的标准方式。
JavaScript 伪协议
javascript:
开头的链接被称为 JavaScript 伪协议。这是一个早期 Web 开发中的特殊协议,允许在 HTML 元素的 href 属性或浏览器地址栏中直接执行 JavaScript 代码。
html1<a href="javascript:alert('Hello World')">点击我</a>
(这个现代浏览器还会弹的)
html1<a href="javascript:var x=1;console.log(x);void 0;">多语句</a>
不过,JavaScript 伪协议存在严重的安全风险,比如XSS 攻击。
html1<a href="javascript:alert(document.cookie)">看似正常的链接</a>
所以内容安全策略经常会禁止使用 JavaScript 伪协议:
header1Content-Security-Policy: script-src 'self'
这也是为什么现代 Web 开发中不再推荐使用 JavaScript 伪协议。
void 与现代前端
阻止链接默认行为
在现代 JavaScript 中,阻止链接默认行为还有更好的办法:
html1<!-- 使用 preventDefault() --> 2<a href="#" onclick="event.preventDefault(); doSomething()">点击我</a>
不过,如果你这个玩意儿点击之后是执行一段 Javascript 代码,那你为啥要用 <a>
标签啊?
html1<!-- 或者直接使用按钮 --> 2<button onclick="doSomething()">点击我</button>
处理箭头函数
void
运算符在现代 JavaScript 中还有个用途,就是处理箭头函数的返回值。箭头函数也是 ES6 之后的新特性了,假如我们箭头函数只是调用一个其它函数,但是箭头函数在不写 {}
的时候,默认会返回调用表达式的值。而我们又有可能对返回值的类型有约束,这时候就可以用 void
运算符来处理:
typescript1type VoidFunc = () => void; 2const fn: VoidFunc = () => void someExpression();
当然,你也完全可以:
typescript1type VoidFunc = () => void; 2const fn: VoidFunc = () => { 3 someExpression(); 4};
立即执行表达式
javascript1void function() { 2 console.log("这个函数会立即执行"); 3}();
由于 void
是个运算符,它将函数声明转换为函数表达式,这样就可以直接括号调用了。
不过现在似乎一般都这么写:
javascript1(function() { 2 console.log("这个函数会立即执行"); 3})();