【译】对象加数组不等于0(含自己总结)

我心飞翔 分类:javascript

你可能看过Gary Bernhardt的 WAT的演讲,他在其中谈到了这些令人困惑的JavaScript代码:

{} + []  // 0
[] + {} // "[object Object]" 【译者注:Object.prototype.toString.call({})的结果是"[object Object]"】
[] + [] // "" 【译者注】
{} + {} // "[object Object][object Object]"  【译者注】
 

他向观众展示了一个对象和一个数组相加结果是0,但是一个数组和一个对象相加结果是"[object Object]"。瞧,如果您在chrome控制台中尝试此操作,那么您将获得:

image.png

但是,如果将这些行用括号括起来,则可以看到这两行的计算结果相同:

image.png

发生什么了?让我们看一下抽象语法树,看看语法上是否有奇怪的事情发生。

如果我们将{} + []插入ASTExplorer,得到这个:

image.png

一个空对象后面跟着一个一元+操作符?那不是对象加数组!那是……完全不同的东西。

为了证实我们的怀疑,我们看到Javascript的作用于数组的一元+运算符的值为0,表明我们的解释确实正确。空代码块被评估为noop,剩下的就是一元表达式:

image.png

我们再去ASTExplorer 以确保[] + {}实际上像我们期望的那样向对象添加了一个数组:

image.png

是的!我们的理解在这里看起来不错!因此,如果我们对语法感到困惑,请将其插入ASTExplorer,然后一切都会显示出来,对吗?

但是故事还没结束。还记得我们如何将某些内容粘贴到ASTExplorer中以查看其解析,然后将其粘贴到Chrome控制台中以查看其行为吗?让我们使用常规的JavaScript对象尝试相同的过程。
Chrome会执行我们期望的操作并创建一个对象:

image.png

但是,当将其粘贴到ASTExplorer中时,将得到以下内容:

image.png

什么?为什么ASTExplorer认为这是语法错误?我们正在声明一个对象!这就像JavaScript的第一天! ASTExplorer怎能不赶上呢?
事实证明ASTExplorer实际上就在这里,而Chrome正是其中一种怪异的表现。任何其他语句之外的裸对象都不会被解析为裸对象,而是被解析为一个代码块,就像之前一样!由于代码块的内部内容不作为语句进行解析,因此解析失败,从而导致冒号上的语法错误。
通过eval可以确信这是对的:

image.png

当Chrome控制台出现明显的语法错误时,为什么最终会创建对象?
Chrome之所以这样做,是因为当您将某些内容粘贴到Chrome控制台中时,您本能地拥有两种不同的行为期望:

  1. Chrome控制台会计算语句。
  2. 将表达式粘贴到Chrome控制台中应计算该表达式。

这两个期望从根本上彼此冲突,并且无法调和。它们导致了如何解析裸对象的根本矛盾。
为了弥补这一差距,Chrome浏览器会动态找出是将行解析为“语句”还是“表达式”。作为表达式,该行最终求值到一个对象,但是作为语句,该行最终出现语法错误。 Chrome改变了他们在此处使用的确切规则,但是他们遵循的规则似乎是这种行为:

  1. 查看输入是否以{开头并以}结尾
  2. 如果不是,则将输入解析为一系列语句(statement)
  3. 如果是,则将输入解析为表达式(expression)
  4. 如果失败,则回过头来将输入解析为一系列语句

我大约有90%的把握就是今天的样子,但是我也没有像一年前那样检查,而且我也不会深入研究Chrome源码来再次解决问题。

总结下:

  • 对象加数组不是0,及时在Chrome控制台看起来是0
  • 如果对语法感到疑惑,使用ASTExplorer
  • Chrome控制台是计算语句和表达式的好方法,但是...
  • Chrome控制台有一些狭窄的陷阱
  • 如果你还是感到困惑,eval可以告诉你真相的来源

译者总结的相关"八股文"及其相关理解:

加法会进行隐式转换,规则是调用其 valueOf() 或 toString() 以取得一个原始值。如果两个值中的任何一个是字符串,则进行字符串拼接,否则进行数字加法。

[] 和 {} 的 valueOf() 都返回对象自身,所以都会调用 toString()。

[].toString() 返回空字符串,({}).toString() 返回“[object Object]”。

+[] // 0 【对一个空数组执行正号运算,实际上就是把数组转型为数字。先调用 [].valueOf()返回自身,继续调用[].toString(),返回空字符串。空字符串转型为数字0】
+{} // NaN
[].toString() // ""
{}.toString() // Uncaught SyntaxError: Unexpected token '.'
({}).toString() // "[object Object]"
[] + {} // "[object Object]" 【隐式转换为: "" + "[object Object]"】
{} + []  // 0 【{}被解析为empty block了。这里本质上是对一个空数组执行正号运算,实际上就是把数组转型为数字。】
[] + [] // "" 【隐式转换为两个空字符串相加】
{} + {} // "[object Object][object Object]"
({} + []) // "[object Object]" 【{}被解析为空对象,本质上和上面的 []+{} 类似】
 

参考:

  • 原文Object Plus Array Is Not Zero

  • Javascript 中 {}+[] === 0 为true因为啥

  • JS中{}+[]和[]+{}的返回值情况是怎样的?

回复

我来回复
  • 暂无回复内容