对于CodeMash 2012的“Wat”演讲中提到的这些奇怪的JavaScript行为,有什么解释?

CodeMash 2012'Wat'演讲基本上指出了Ruby和JavaScript的一些奇怪的怪癖。

我在http://jsfiddle.net/fe479/9/上做了一个JSFiddle的结果。

下面列出了特定于JavaScript的行为(因为我不知道Ruby)。

我在JSFiddle中发现,我的一些结果与video中的结果不一致,我不知道为什么。 但是,我很想知道JavaScript在各种情况下如何处理幕后的工作。

Empty Array + Empty Array [] + [] result: <Empty String> 

在JavaScript中使用数组时,我对+运算符非常好奇。 这与video的结果相符。

 Empty Array + Object [] + {} result: [Object] 

这与video的结果相符。 这里发生了什么? 为什么这是一个对象。 +运营商做什么?

 Object + Empty Array {} + [] result [Object] 

这与video不符。 video表明,结果是0,而我得到[对象]。

 Object + Object {} + {} result: [Object][Object] 

这也不符合video,如何输出一个variables结果在两个对象? 也许我的JSFiddle是错误的。

 Array(16).join("wat" - 1) result: NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN 

做wat + 1结果在wat1wat1wat1wat1

我怀疑这只是简单的行为,试图从string中减去一个数字导致NaN。

下面是你看到的结果(应该看到的结果)的解释清单。 我使用的参考文献来自ECMA-262标准 。

  1. [] + []

    使用加法运算符时,左边和右边的操作数先被转换为基元( §11.6.1 )。 根据§9.1 ,将一个对象(在本例中是一个数组)转换为一个基元返回它的默认值,这对于具有有效的toString()方法的对象是调用object.toString() ( §8.12.8 )的结果。 对于数组,这与调用array.join() (第15.4.4.2节 )相同。 join一个空数组会导致一个空string,所以加法运算符的第7步返回两个空string的连接,即空string。

  2. [] + {}

    [] + []类似,两个操作数都先转换为基元。 对于“对象对象”(第15.2节),这又是调用object.toString()的结果,对非空的非未定义的对象是"[object Object]" (第15.2.4.2节 )。

  3. {} + []

    这里的{}不是作为一个对象来分析的,而是作为一个空白的块( §12.1 ,至less只要你不强迫这个语句成为一个expression式,而是稍后再说)。 空块的返回值是空的,所以该语句的结果与+[]相同。 一元+运算符( §11.4.6 )返回ToNumber(ToPrimitive(operand)) 。 正如我们已经知道的, ToPrimitive([])是空string,根据§9.3.1 , ToNumber("")是0。

  4. {} + {}

    类似于前面的情况,第一个{}被parsing为一个返回值为空的块。 同样, +{}ToNumber(ToPrimitive({})) ,而ToPrimitive({})"[object Object]" (请参阅[] + {} )。 所以要得到+{}的结果,我们必须在string"[object Object]"上应用ToNumber 。 按照§9.3.1的步骤,我们得到NaN

    如果语法不能将String解释为StringNumericLiteral的扩展,那么ToNumber的结果是NaN

  5. Array(16).join("wat" - 1)

    根据§15.4.1.1和§15.4.2.2 , Array(16)创build了一个长度为16的新数组。为了得到要join的参数的值, §11.6.2步骤#5和#6显示我们必须转换两个操作数都使用ToNumberToNumber(1)简单地是1( §9.3 ),而ToNumber("wat")又是NaN根据§9.3.1 。 根据第11.6.2 节的第7 步 , §11.6.3规定

    如果任一操作数是NaN ,则结果是NaN

    所以Array(16).join的参数是NaN 。 在§15.4.4.5( Array.prototype.join )之后,我们必须在参数"NaN" ( §9.8.1 )上调用ToString

    如果mNaN ,则返回string"NaN"

    在第15.4.4.5节的第10步之后,我们得到15个"NaN"和空string的重复,这就等于你所看到的结果。 当使用"wat" + 1而不是"wat" - 1作为参数时,加法运算符将1转换为string,而不是将"wat"转换为数字,所以它有效地调用Array(16).join("wat1")

至于为什么你看到{} + []情况的不同结果:当用它作为一个函数参数时,你迫使这个语句成为一个ExpressionStatement ,这使得不可能将空分析为{} ,所以它被parsing为一个空的对象文字。

这是一个比答案更多的评论,但由于某种原因,我不能评论你的问题。 我想纠正你的JSFiddle代码。 不过,我把这张贴在“黑客新闻”上,有人build议我在这里重新发布。

JSFiddle代码中的问题是({}) (在括号内打开大括号)与{} (打开大括号作为一行代码的开头)不一样。 所以,当你inputout({} + [])你强迫{}成为当你键入{} + []时不是的东西。 这是Javascript的整体“wat'-ness的一部分。

基本的想法很简单JavaScript想要允许这两种forms:

 if (u) v; if (x) { y; z; } 

为了做到这一点,有两个解释,一个是大括号:1.它不是必需的 ,2.它可以出现在任何地方

这是一个错误的举动。 真正的代码没有在中间出现的大括号,而真正的代码在使用第一种forms而不是第二种forms时往往更脆弱。 (大约每隔一个月在我上一次工作的时候,当他们对我的代码的修改不起作用时,我会被打电话给同事的办公桌,问题是他们添加了一行“if”而不添加curl大括号,最终我只是养成了花括号总是需要的习惯,即使你只写了一行。)

幸运的是,在许多情况下,eval()将复制JavaScript的全部function。 JSFiddle代码应为:

 function out(code) { function format(x) { return typeof x === "string" ? JSON.stringify(x) : x; } document.writeln('&gt;&gt;&gt; ' + code); document.writeln(format(eval(code))); } document.writeln("<pre>"); out('[] + []'); out('[] + {}'); out('{} + []'); out('{} + {}'); out('Array(16).join("wat" + 1)'); out('Array(16).join("wat - 1")'); out('Array(16).join("wat" - 1) + " Batman!"'); document.writeln("</pre>"); 

[这也是我多年来第一次编写document.writeln,写document.writeln()和eval()的任何东西都会让我觉得有点肮脏。]

我第二@ Ventero的解决scheme。 如果你愿意的话,你可以详细了解如何转换它的操作数。

第一步(§9.1):将两个操作数转换为原语(原始值是undefinednull ,布尔值,数字,string;其他值都是对象,包括数组和函数)。 如果一个操作数已经是原始的,那么就完成了。 如果不是,则它是obj并执行以下步骤:

  1. 调用obj.valueOf() 。 如果它返回一个原语,你就完成了。 Object和数组的直接实例返回自己,所以你还没有完成。
  2. 调用obj.toString() 。 如果它返回一个原语,你就完成了。 {}[]都返回一个string,所以你完成了。
  3. 否则,抛出一个TypeError

对于date,第1步和第2步交换。 您可以观察转换行为,如下所示:

 var obj = { valueOf: function () { console.log("valueOf"); return {}; // not a primitive }, toString: function () { console.log("toString"); return {}; // not a primitive } } 

交互( Number()首先转换为原始,然后转换为数字):

 > Number(obj) valueOf toString TypeError: Cannot convert object to primitive value 

第二步(§11.6.1):如果其中一个操作数是一个string,另一个操作数也转换为string,结果是通过连接两个string产生的。 否则,两个操作数都转换为数字,结果是通过添加它们而产生的。

转换过程更详细的解释:“ 什么是{} + {}在JavaScript中? ”

我们可以参考这个规范,这是很好的,也是最准确的,但是大多数情况下也可以用更易理解的方式来解释,

  • +-运算符只能使用原始值。 更具体地说, + (加法)与string或数字一起工作,而+ (一元)和- (减法和一元)只能与数字一起工作。
  • 所有原始函数或运算符都希望将原始值作为参数,将首先将该参数转换为所需的基本types。 它是通过valueOftoString来完成的,它可以在任何对象上使用。 这就是为什么这些函数或操作符在对象上调用时不会抛出错误的原因。

所以我们可以这样说:

  • [] + []String([]) + String([])相同, '' + ''相同。 我在上面提到, + (加法)对于数字也是有效的,但是在JavaScript中没有有效的数组表示forms,所以使用了string的添加。
  • [] + {}String([]) + String({})相同,与'' + '[object Object]'
  • {} + [] 。 这一个值得更多的解释(见Ventero答案)。 在这种情况下,花括号不是作为一个对象来处理,而是作为一个空白块来处理,所以它和+[]是一样的。 一元+只适用于数字,所以实现试图从[]获得一个数字。 首先它尝试valueOf在数组的情况下返回相同的对象,然后它尝试最后的手段: toString结果转换为一个数字。 我们可以把它写成+Number(String([])) ,它和+Number('')相同。
  • Array(16).join("wat" - 1)减法-仅适用于数字,因此它与Array(16).join(Number("wat") - 1) ,因为"wat"不能被转换为有效的号码。 我们收到NaN ,并且NaN任何算术运算的结果都是NaN ,所以我们有: Array(16).join(NaN)

支持早些时候分享的内容。

这种行为的根本原因部分是由于JavaScript的弱types本质。 例如,expression式1 +“2”是不明确的,因为根据操作数types(int,string)和(int int)有两种可能的解释:

  • 用户打算连接两个string,结果:“12”
  • 用户打算添加两个数字,结果:3

因此,随着inputtypes的变化,输出可能性会增加。

加法algorithm

  1. 强制操作数为原始值

JavaScript基元是string,数字,空值,未定义和布尔值(符号即将在ES6中出现)。 任何其他值是一个对象(例如数组,函数和对象)。 因此描述了将对象转换为原始值的强制过程:

  • 如果在调用object.valueOf()时返回一个原始值,则返回此值,否则继续

  • 如果调用object.toString()时返回一个原始值,则返回该值,否则继续

  • 抛出一个TypeError

注意:对于date值,顺序是在valueOf之前调用toString。

  1. 如果任何操作数值是一个string,那么做一个string连接

  2. 否则,将两个操作数转换为它们的数值,然后添加这些值

了解JavaScript中的各种强制值types确实有助于使混淆输出变得更清晰。 请参阅下面的强制表

 +-----------------+-------------------+---------------+ | Primitive Value | String value | Numeric value | +-----------------+-------------------+---------------+ | null | “null” | 0 | | undefined | “undefined” | NaN | | true | “true” | 1 | | false | “false” | 0 | | 123 | “123” | 123 | | [] | “” | 0 | | {} | “[object Object]” | NaN | +-----------------+-------------------+---------------+ 

知道JavaScript的+操作符是左关联的也是很好的,因为这决定了输出将是涉及多于一个+操作的情况。

利用如此1 +“2”将给“12”,因为任何涉及string的添加将始终默认为string连接。

你可以在这个博客文章中阅读更多的例子(我写的免责声明)。