协慌网

登录 贡献 社区

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

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。

答案

Here's a list of explanations for the results you're seeing (and supposed to be seeing). The references I'm using are from the ECMA-262 standard.

  1. [] + []

    When using the addition operator, both the left and right operands are converted to primitives first (§11.6.1). As per §9.1, converting an object (in this case an array) to a primitive returns its default value, which for objects with a valid toString() method is the result of calling object.toString() (§8.12.8). For arrays this is the same as calling array.join() (§15.4.4.2). Joining an empty array results in an empty string, so step #7 of the addition operator returns the concatenation of two empty strings, which is the empty string.

  2. [] + {}

    Similar to [] + [], both operands are converted to primitives first. For "Object objects" (§15.2), this is again the result of calling object.toString(), which for non-null, non-undefined objects is "[object Object]" (§15.2.4.2).

  3. {} + []

    The {} here is not parsed as an object, but instead as an empty block (§12.1, at least as long as you're not forcing that statement to be an expression, but more about that later). The return value of empty blocks is empty, so the result of that statement is the same as +[]. The unary + operator (§11.4.6) returns ToNumber(ToPrimitive(operand)). As we already know, ToPrimitive([]) is the empty string, and according to §9.3.1, ToNumber("") is 0.

  4. {} + {}

    Similar to the previous case, the first {} is parsed as a block with empty return value. Again, +{} is the same as ToNumber(ToPrimitive({})), and ToPrimitive({}) is "[object Object]" (see [] + {}). So to get the result of +{}, we have to apply ToNumber on the string "[object Object]". When following the steps from §9.3.1, we get NaN as a result:

    If the grammar cannot interpret the String as an expansion of StringNumericLiteral, then the result of ToNumber is NaN.

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

    As per §15.4.1.1 and §15.4.2.2, Array(16) creates a new array with length 16. To get the value of the argument to join, §11.6.2 steps #5 and #6 show that we have to convert both operands to a number using ToNumber. ToNumber(1) is simply 1 (§9.3), whereas ToNumber("wat") again is NaN as per §9.3.1. Following step 7 of §11.6.2, §11.6.3 dictates that

    If either operand is NaN, the result is NaN.

    So the argument to Array(16).join is NaN. Following §15.4.4.5 (Array.prototype.join), we have to call ToString on the argument, which is "NaN" (§9.8.1):

    If m is NaN, return the String "NaN".

    Following step 10 of §15.4.4.5, we get 15 repetitions of the concatenation of "NaN" and the empty string, which equals the result you're seeing. When using "wat" + 1 instead of "wat" - 1 as argument, the addition operator converts 1 to a string instead of converting "wat" to a number, so it effectively calls Array(16).join("wat1").

As to why you're seeing different results for the {} + [] case: When using it as a function argument, you're forcing the statement to be an ExpressionStatement, which makes it impossible to parse {} as empty block, so it's instead parsed as an empty object literal.

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

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

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

if (u)
    v;

if (x) {
    y;
    z;
}

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

这是一个错误的举动。真正的代码没有出现在不知名的中间的开括号,并且当使用第一个表单而不是第二个表单时,真正的代码也往往更脆弱。 (在我上一份工作中大约每隔一个月,当我们对我的代码的修改不起作用时,我会被叫到同事的办公桌,问题是他们在 “if” 中添加了一行而没有添加卷曲我最终只是采用了花括号总是需要的习惯,即使你只写了一行。)

幸运的是,在许多情况下,eval()将复制 JavaScript 的全部功能。 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 的第二个解决方案。如果你愿意,你可以详细了解如何+转换其操作数。

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

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

对于日期,将交换步骤 1 和 2。您可以按如下方式观察转换行为:

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

交互( Number()首先转换为 primitive,然后转换为 number):

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

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

有关转换过程的更详细说明:“ JavaScript 中的 {} + {} 是什么?