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

2022-08-29 22:28:44

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

我已经在 http://jsfiddle.net/fe479/9/ 对结果进行了JSFiddle。

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

我在JSFiddle中发现我的一些结果与视频中的结果不符,我不知道为什么。然而,我很好奇JavaScript在每种情况下都是如何处理幕后工作的。

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

我对JavaScript中数组一起使用时的运算符非常好奇。这与视频的结果相匹配。+

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

这与视频的结果相匹配。这是怎么回事?为什么这是一个对象。操作员做什么?+

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

这与视频不匹配。视频表明结果是0,而我得到的是[对象]。

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

这也与视频不匹配,输出变量如何导致两个对象?也许我的JSFiddle是错的。

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

做 wat + 1 结果 ...wat1wat1wat1wat1

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


答案 1

以下是您看到(并且应该看到)的结果的解释列表。我使用的参考文献来自 ECMA-262 标准

  1. [] + []

    使用加法运算符时,左操作数和右操作数首先转换为基元 (§11.6.1)。根据 §9.1,将对象(在本例中为数组)转换为基元将返回其默认值,对于具有有效方法的对象,该默认值是调用的结果 (§8.12.8)。对于数组,这与调用 (§15.4.4.2) 相同。联接空数组会导致空字符串,因此 add 运算符的步骤 #7 返回两个空字符串(即空字符串)的串联。toString()object.toString()array.join()

  2. [] + {}

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

  3. {} + []

    这里不是作为一个对象来解析的,而是作为一个空块来解析的(§12.1,至少只要你不强迫该语句成为表达式,但稍后会详细介绍)。空块的返回值为空,因此该语句的结果与 相同。一元运算符 (§11.4.6) 返回 。正如我们已经知道的,是空字符串,根据§9.3.1,是0。{}+[]+ToNumber(ToPrimitive(operand))ToPrimitive([])ToNumber("")

  4. {} + {}

    与上一种情况类似,第一种情况被解析为具有空返回值的块。同样,与 相同,并且是(请参见)。因此,要获得 的结果,我们必须在字符串上应用 。当按照§9.3.1中的步骤操作时,我们得到的结果如下:{}+{}ToNumber(ToPrimitive({}))ToPrimitive({})"[object Object]"[] + {}+{}ToNumber"[object Object]"NaN

    如果语法不能将字符串解释为 StringNumericLiteral 的扩展,则 ToNumber 的结果是 NaN

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

    根据 §15.4.1.1§15.4.2.2,创建一个长度为 16 的新数组。为了获得要连接的参数的值,§11.6.2 步骤 #5 和 #6 表明我们必须使用 将 两个操作数转换为一个数字。 只是 1 (§9.3),而同样是 §9.3.1。根据 §11.6.2 第 7 步,§11.6.3 规定Array(16)ToNumberToNumber(1)ToNumber("wat")NaN

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

    所以参数是.在 §15.4.4.5 () 之后,我们必须调用参数 ,即 (§9.8.1):Array(16).joinNaNArray.prototype.joinToString"NaN"

    如果 mNaN,则返回字符串“NaN”。

    §15.4.4.5 的步骤 10 之后,我们得到 15 次重复的串联和空字符串,这等于您看到的结果。当使用代替 as 参数时,加法运算符将转换为字符串而不是转换为数字,因此它有效地调用 。"NaN""wat" + 1"wat" - 11"wat"Array(16).join("wat1")

至于为什么你会看到不同的结果:当它用作函数参数时,你强制语句是一个 ExpressionStatement,这使得它不可能解析为空块,所以它被解析为一个空对象文本。{} + []{}


答案 2

这更像是一个评论而不是一个答案,但由于某种原因,我无法评论你的问题。我想纠正你的JSFiddle代码。但是,我在Hacker News上发布了这个,有人建议我在这里重新发布它。

JSFiddle 代码中的问题是(括号内左大括号)与(左大括号作为一行代码的开头)不同。因此,当您键入时,您将强制成为键入 时不是的东西。这是Javascript整体“wat”性的一部分。({}){}out({} + []){}{} + []

基本思想是简单的JavaScript想要允许这两种形式:

if (u)
    v;

if (x) {
    y;
    z;
}

为此,对左大括号进行了两种解释:1.它不是必需的,2.它可以出现在任何地方

这是一个错误的举动。真正的代码没有一个出现在偏僻位置的左大括号,当使用第一种形式而不是第二种形式时,真正的代码往往也更加脆弱。(大约每隔一个月,在我上一份工作中,当他们对我的代码的修改不起作用时,我会被叫到同事的办公桌前,问题是他们在“if”中添加了一行而不添加大括号。我最终只是养成了一个习惯,即大括号总是必需的,即使你只写了一行。

幸运的是,在许多情况下,eval() 将复制 JavaScript 的全部 wat-ness。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() 的东西都有点脏。