首页 文章

对于CodeMash 2012的'Wat'谈话中提到的这些奇怪的JavaScript行为有什么解释?

提问于
浏览
714

'Wat' talk for CodeMash 2012基本上用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]

这匹配视频's result. What' s在这里?为什么这是一个对象 . + 运营商做什么?

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

这与视频不符 . 视频显示结果为0,而我得到[对象] .

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

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

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

做wat 1会导致 wat1wat1wat1wat1 ...

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

5 回答

  • 28

    在这里's a list of explanations for the results you'重新看到(并且应该看到) . 我正在使用的引用来自ECMA-262 standard .

    [] []

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

    [] {}

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

    {} []

    {} 这里没有被解析为一个对象,而是作为一个空块(§12.1,至少只要你不强制该语句成为一个表达式,但稍后会有更多相关内容) . 空块的返回值为空,因此该语句的结果与 +[] 相同 . 一元 + 运算符(§11.4.6)返回 ToNumber(ToPrimitive(operand)) . 我们已经知道, ToPrimitive([]) 是空字符串,根据§9.3.1ToNumber("") 是0 .

    {} {}

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

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

    数组(16).join(“wat” - 1)

    根据§15.4.1.1§15.4.2.2Array(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).join 的论点是 NaN . 遵循§15.4.4.5( Array.prototype.join ),我们必须在参数上调用 ToString ,即 "NaN"§9.8.1):

    如果m是NaN,则返回字符串“NaN” .

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

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

  • 13

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

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

    基本的想法是简单的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()的东西都很脏 .

  • 16

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

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

    • 致电 obj.valueOf() . 如果它返回一个原语,那么你就完成了 . Object 和数组的直接实例返回自己,所以你还没有完成 .

    • 致电 obj.toString() . 如果它返回一个原语,那么你就完成了 . {}[] 都返回一个字符串,所以你就完成了 .

    • 否则,抛出 TypeError .

    对于日期,步骤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
    

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

    转换过程的更详细解释:“What is {} + {} in JavaScript?

  • 0

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

    • +- 运算符仅适用于原始值 . 更具体地说 + (加法)适用于字符串或数字, + (一元)和 - (减法和一元)仅适用于数字 .

    • 期望原始值作为参数的所有本机函数或运算符将首先将该参数转换为所需的基本类型 . 它由 valueOftoString 完成,可在任何对象上使用 . 当在对象上调用时,768287 t会抛出错误 .

    所以我们可以这样说:

    • [] + []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) .

  • 1431

    支持之前分享的内容 .

    导致此行为的根本原因部分归因于JavaScript的弱类型特性 . 例如,表达式1“2”是不明确的,因为基于操作数类型(int,string)和(int int)有两种可能的解释:

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

    • 用户打算添加两个数字,结果:3

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

    加法算法

    • 将操作数强制为原始值

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

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

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

    • 抛出TypeError

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

    • 如果任何操作数值是字符串,则执行字符串连接

    • 否则,将两个操作数转换为其数值,然后添加这些值

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

    您可以在this blog post中阅读更多示例(我写的免责声明) .

相关问题