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

Nib*_*Pig 743 javascript

CodeMash 2012"Wat"谈话基本上指出了Ruby和JavaScript的一些奇怪的怪癖.

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

下面列出了JavaScript特有的行为(我不知道Ruby).

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

Empty Array + Empty Array
[] + []
result:
<Empty String>
Run Code Online (Sandbox Code Playgroud)

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

Empty Array + Object
[] + {}
result:
[Object]
Run Code Online (Sandbox Code Playgroud)

这与视频的结果相匹配.这里发生了什么?为什么这是一个对象.什么是+运营商吗?

Object + Empty Array
{} + []
result
[Object]
Run Code Online (Sandbox Code Playgroud)

这与视频不符.该视频表明结果是0,而我得到[对象].

Object + Object
{} + {}
result:
[Object][Object]
Run Code Online (Sandbox Code Playgroud)

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

Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Run Code Online (Sandbox Code Playgroud)

做wat + 1导致wat1wat1wat1wat1......

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

Ven*_*ero 1471

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

  1. [] + []

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

  2. [] + {}

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

  3. {} + []

    {}这里不会被解析为一个对象,而是作为一个空块(§12.1,只要你不逼这种说法至少是一个表达式,但稍后详细说明).空块的返回值为空,因此该语句的结果与之相同+[].一元运算+符(第11.4.6节)返回ToNumber(ToPrimitive(operand)).我们已经知道,ToPrimitive([])是空字符串,根据§9.3.1,ToNumber("")是0.

  4. {} + {}

    与前一种情况类似,第一种情况{}被解析为具有空返回值的块.再次,+{}是一样的ToNumber(ToPrimitive({})),和ToPrimitive({})"[object Object]"(见[] + {}).所以为了获得结果+{},我们必须ToNumber在字符串上应用"[object Object]".当遵循§9.3.1中的步骤时,我们得到NaN结果:

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

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

    根据§15.4.1.1§15.4.2.2,Array(16)创建一个长度为16的新数组.要获取要加入的参数的值,§11.6.2步骤#5和#6表明我们必须将两个操作数转换为数字使用ToNumber.ToNumber(1)被简单地1(第9.3节),而ToNumber("wat")再次NaN§9.3.1.遵循§11.6.2的第7 ,§11.6.3规定了这一点

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

    所以论证Array(16).joinNaN.遵循§15.4.4.5(Array.prototype.join),我们必须调用ToString参数,即"NaN"(§9.8.1):

    如果mNaN,则返回String "NaN".

    按照§15.4.4.5的步骤10 ,我们得到15个重复的连接"NaN"和空字符串,这等于你看到的结果.当使用"wat" + 1而不是"wat" - 1作为参数时,加法运算符转换1为字符串而不是转换"wat"为数字,因此它有效地调用Array(16).join("wat1").

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

  • @RobElsner` [] + 1`几乎遵循与`[] + []`相同的逻辑,只是将`1.toString()`作为rhs操作数.对于`[] -1`,请参见第5点中"wat"-1的解释.记住`ToNumber(ToPrimitive([]))`是0(第3点). (4认同)
  • 这个解释缺失/省略了很多细节.例如,"转换被摄体(在这种情况下,数组)为原始返回其默认值,其用于与一个有效的toString对象()方法是调用object.toString()的结果"是完全缺少的[]的valueOf是首先调用,但因为返回值不是基元(它是一个数组),所以使用[]的toString代替.我建议看看这个,而不是真正的深入解释http://www.2ality.com/2012/01/object-plus-object.html (4认同)
  • 那么,为什么[] +1 =&gt;“ 1”和[] -1 =&gt; -1? (2认同)

CR *_*ost 30

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

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

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

if (u)
    v;

if (x) {
    y;
    z;
}
Run Code Online (Sandbox Code Playgroud)

为此,对开括号进行了两种解释: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>");
Run Code Online (Sandbox Code Playgroud)

[这也是我多年来第一次写document.writeln,我觉得写一些涉及document.writeln()和eval()的东西都很脏.

  • "这是一个错误的举动.真正的代码没有出现在不通的中间的开括号 - 我不同意(有点):我经常在过去使用这样的块来限制变量_in C_.当嵌入式C中堆栈上的变量占用空间时,这个习惯被捡了一段时间,所以如果不再需要它们,我们希望在块的末尾释放空间.但是,ECMAScript仅限于function(){}块中的范围.所以,虽然我不同意这个概念是错误的,但我同意JS中的实现是(_possibly_)错误的. (15认同)
  • @JessTelford在ES6中,您可以使用`let`来声明块范围的变量. (4认同)

Axe*_*yer 18

我是@Ventero的第二个解决方案.如果您愿意,您可以详细了解如何+转换其操作数.

第一步骤(第9.1节):两个操作数转换成原语(原始值undefined,null,布尔值,数字,字符串;所有其它值都是对象,包括数组和功能).如果操作数已经是原始的,那么你就完成了.如果不是,则它是一个对象,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
    }
}
Run Code Online (Sandbox Code Playgroud)

交互(Number()首先转换为基元然后转换为数字):

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value
Run Code Online (Sandbox Code Playgroud)

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

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


Mar*_*wak 13

我们可以参考规范,这很好,最准确,但大多数情况也可以通过以下语句以更易于理解的方式解释:

  • +并且-运算符仅适用于原始值.更具体地说+(加法)适用于字符串或数字,+(一元)和-(减法和一元)仅适用于数字.
  • 期望原始值作为参数的所有本机函数或运算符将首先将该参数转换为所需的基本类型.它是用valueOf或完成的,toString可以在任何对象上使用.这就是为什么这些函数或运算符在对象上调用时不会抛出错误的原因.

所以我们可以这样说:

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