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




javascript阮一峰 (4)

以下是你看到的结果(应该看到的结果)的解释清单。 我使用的参考文献来自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.1ToNumber("")是0。

  4. {} + {}

    与前面的情况类似,第一个{}被解析为具有空返回值的块。 再次, +{}ToNumber(ToPrimitive({})) ,并且ToPrimitive({})"[object Object]" (请参阅[] + {} )。 所以要得到+{}的结果,我们必须在字符串"[object Object]"上应用ToNumber 。 当遵循§9.3.1的步骤时,我们得到了NaN

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

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

    根据§15.4.1.1§15.4.2.2Array(16)创建了一个长度为16的新数组。为了获得要加入的参数的值, §11.6.2步骤#5和#6表明我们必须转换两个操作数都使用ToNumber编号。 ToNumber(1)简单地为1( ToNumber ),而ToNumber("wat")又是NaN ,按§9.3.1 。 遵循第§11.6.2 §11.6.3第7 §11.6.2§11.6.3规定

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

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

    如果mNaN ,则返回字符串"NaN"

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

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

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 + Object
{} + {}
result:
[Object][Object]

这与视频不匹配,以及如何将变量结果输出到两个对象? 也许我的JSFiddle是错误的。

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

做wat + 1结果在wat1wat1wat1wat1 ...

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


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

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

所以我们可以这样说:

  • [] + []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"不能被转换为有效的号码。 我们接收NaN ,并且NaN任何算术运算都以NaN结果,所以我们有: Array(16).join(NaN)

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

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

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

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

加法算法

  1. 强制操作数为原始值

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

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

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

  • 抛出一个TypeError

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

  1. 如果任何操作数值是一个字符串,则执行字符串连接

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

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

+-----------------+-------------------+---------------+
| 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”,因为任何涉及字符串的添加将始终默认为字符串连接。

您可以在此博客文章中阅读更多示例(我写它的免责声明)。


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

JSFiddle代码中的问题是({}) (括号内的开放大括号)与{} (作为一行代码的开头处打开大括号)不一样。 因此,当您键入out({} + [])您将强制{}成为键入{} + []时不会显示的内容。 这是Javascript的整体'wat'-ness的一部分。

基本思想很简单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()的任何东西都感到有点肮脏。]







javascript