惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
H
Heimdal Security Blog
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
小众软件
小众软件
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Cloudbric
Cloudbric
AI
AI
WordPress大学
WordPress大学
博客园 - 聂微东
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
博客园 - Franky
V
V2EX
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
H
Help Net Security
量子位
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
C
Cisco Blogs
S
Security Affairs

Surmon.me

一人有限集团 你就是不敢 创造力是温柔的谎言 人类正在退出人类 AI 代替不了这样的你 脉冲点火背后的架构设计 基于 Cloudflare 生态的 AI Agent 实现 NodePress 支持用户登录了 从统计学习到通用智能 2025 投资报告:走慢的路 无依之地 会杀人的菩萨 无我不是共识 文化的积重与偏见 当下即安 科学的尽头是态度 无我不是 Egoless 信仰不因恐惧而存在 世间无解的矛与盾 先别急着做些什么 佛不需要你的皈依 真理的幻觉 两扇大门 造心里的浮屠 自胜者强 逻辑与智慧 真的相 快乐的秘密 只需愿意 是名体验 最深沉的梦 森林里倒下的树 现象与相 庸人自扰 React 与 Vue 的完美融合 开心就好吗 与原生家庭和解 两支毒箭 怎么自净其意 一尘不染 一片白云 让风穿膛而过 喝下去,然后闭嘴 活在当下 当纠结发生时 唯「我」独尊 一场把戏(时间) 一场把戏(二元对立) “我” 和 “我” 谁是众生? 船里没有人 玫瑰不需要说话 没有人能渡任何人 你我为轮,使之不再 当我们在谈论独立时 自我约束就是最好的自我接纳 从善与恶的表象出发 佛法不是心理学 别人眼中的你并不是你 高高山上走 祝你心想事成 祝你的噩梦早日发生 道是无情却有情 裙子只为自己而穿 不要盲目地评估自己 先别急着 “爱自己” 让生命欣然舞动 你无法复制乔布斯 符号不是目的 如果一件事发生了 每当我起心动念时 爱出者爱返 理解不是尊重的基础 在没有解决自己的问题之前 “知足常乐” 不是比较 逻辑是无法成为信仰的 买不到的自由 成为诵经者 伤口听不懂大道理 是立场让你不自由 - Unfreedom 天雨大不润无根之草 做自己的神 当心诱惑本身 牛洞冥思录 修罗启示录 让大脑自举 我更喜欢叫它无梦寺 存在主义也许不是解药 出南洋记 2022 的所有答复 没意义的表达 思考的记录 故宫暂行之幻想 极端的比较 解构的终极 舍利子是如何炼成的 心要野 佛教的偏见 文艺青年 我是动物
JavaScript 中的 void 操作符
Surmon · 2017-06-29 · via Surmon.me

在 JavaScript 中,当我想取屋子里的第一条 dog 时,首先要判断 house 对象是否存在,然后再判断 house.dogs 是否存在,最后取 house.dogs[0]

在 JS 需要这么写:

        
        

1

var dog = (typeof house !== 'undefined && house !== null) && house.dogs && house.dogs[0]

在 Coffee 中,我只需要这么写:

        
        
      

Coffee 的本质就是 JS,之所以 Coffee 能工作的很好,是因为它生成出了高效而且健壮的 JS 代码,我们可以看看它的生成结果。

        
        

12

var dog, _ref; dog = typeof house !== "undefined" && house !== null ? (_ref = house.dogs) != null ? _ref[0] : void 0 : void 0;

短短一行 Coffee 代码生成出了如此长的 JS 代码,看上去似乎比我最前面自己用 JS 写的更靠谱更安全,末尾还出来了两个 void 0,这究竟是何方神圣?

结构化一下上面的例子:

        
        

123

dog = (typeof house !== "undefined" && house !== null) ? ((_ref = house.dogs) != null ? _ref[0] : void 0 ) : void 0;

如果 house 未定义或 housenull 时,返回 void 0 如果 house.dogsnull 时,返回 void 0void 0 究竟是什么值,这个倒很容易测试:

        
        

12

typeof void 0 // 得到 "undefined" console.log(void 0) // 输出 undefined

似乎 void 0 就是 undefined 了,但这样子路数太野,也不够严谨,即无法回答:void 100; void hello(); void i++ 这无数可能组合的值是什么?

规范是这么说的

在 ECMAScript 262 规范,有如下描述:

        
        

123456

The void Operator The production UnaryExpression : void UnaryExpression is evaluated as follows: - Let expr be the result of evaluating UnaryExpression. - Call GetValue(expr). - Return undefined. NOTE: GetValue must be called even though its value is not used because it may have observable side-effects.

搬译一下:

        
        

1234567

void 操作符 产生式 UnaryExpression : void UnaryExpression 按如下流程解释: - 令 expr 为解释执行 UnaryExpression 的结果。 - 调用 GetValue(expr). - 返回 undefined. 注意:GetValue 一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。

重点在于:无论 void 后的表达式是什么,void 操作符都会返回 undefined,因此上面由 Coffee 编译出来的代码我们可以认为是这样的:

        
        

123

dog = (typeof house !== "undefined" && house !== null) ? ((_ref = house.dogs) != null ? _ref[0] : undefined ) : undefined ;

问题来了,既然 (void 0) === undefined,那直接写 undefined 不就行了么?

为什么要用 void

因为 undefined 在 JavaScript 中不是保留字。换言之,你可以写出:

        
        

12345

function joke() { var undefined = "hello world"; console.log(undefined); // 会输出"hello world" } console.log(undefined); // 输出undefined

对的,你可以在一个函数上下文内以 undefined 做为变量名,于是在这个上下文写的代码便只能通过从全局作用域来取到 undefined,如:

        
        

12

window.undefined // 浏览器环境 GLOBAL.undefined // Node环境

但要注意的是,即便 window GLOBAL 仍然可以在函数上下文被定义,故从 window/GLOBAL 上取 undefined 并不是 100% 可靠的做法。如:

        
        

1234567891011

function x() { var undefined = 'hello world', f = {}, window = { 'undefined': 'joke' }; console.log(undefined);// hello world console.log(window.undefined); //joke console.log(f.a === undefined); //false console.log(f.a === void 0); //true }

于是,采用 void 方式获取 undefined 便成了通用准则。如 underscore.js 里的 isUndefined 便是这么写的:

        
        

123

_.isUndefined = function(obj) { return obj === void 0; }

除了采用 void 能保证取到 undefined 值以外,还有其它方法吗?有的,还有一种方式是通过函数调用。如 angular.js 的源码里就用这样的方式:

        
        

123

(function(window, document, undefined) { //..... })(window, document);

通过不传参数,确保了 undefined 参数的值是一个 undefined

其它作用

除了取 undefined 外,void 还有什么其它用处吗?

还有一个常见的功能,填充 href。下面是一个微博截图,它的转发、收藏、讨论都是超链接,但是用户并不希望点击它们会跳转到另一个页面,而是引发出一些交互操作。

理论上而言,这三个超链接都是没有 URL 的,但如果不写的话,呵呵,点击它会刷新整个页面。于是便用上了 href="javascript:void(0) 的方式,确保点击它会执行一个纯粹无聊的 void(0)

另一种情况是,如果我们要生成一个空的 srcimage,最好的方式似乎也是 src='javascript:void(0)',参见 StackOverflow 上的这个问题: What's the valid way to include an image with no src

写在最后

回到 void 的定义,有一句话特别让人迷惑:

注意:GetValue 一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。

这是什么意思?这表示无论 void 右边的表达式是什么,都要对其求值。这么说可能不太明白,在知乎上 winter 大神有过阐述关于 Js 中的 void,既然返回永远是 undefined,那么 GetValue 有啥用?,我且拾人牙慧,代入一个场景,看代码:

        
        

12345678910111213141516171819

var happiness = 10; var girl = { get whenMarry() { happiness--; return 1/0; // Infinity }, get happiness() { return happiness; } }; console.log(girl.whenMarry); // 调用了 whenMarry 的 get 方法 console.log(girl.happiness); // 9 void girl.whenMarry; // 调用了 whenMarry 的 get 方法 console.log(girl.happiness); // 8 delete girl.whenMarry; // 没有调用 whenMarry 的 get 方法 console.log(girl.happiness); // 还是8

上述代码定义了一个大龄文艺女青年,每被问到什么时候结婚呀(whenMarry),happiness 都会减1。 从执行情况可以看出,无论是普通访问 girl.whenMarry,还是 void girl.whenMarry 都会使她的 happiness--。 而如果把 void 换成 delete 操作符写成 delete girl.whenMarry,她的 happiness 就不会减了,因为 delete 操作符不会对 girl.whenMarry 求值。

总结

void 有如下作用:

  • 通过采用 void 0undefined 比采用字面上的 undefined 更靠谱更安全,应该优先采用 void 0 这种方式。
  • 填充 <a>href 确保点击时不会产生页面跳转; 填充 <image>src,确保不会向服务器发出垃圾请求。

本文内容衍生自 《谈谈Javascript中的void操作符》