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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9032856/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 08:26:36  来源:igfitidea点击:

What is the explanation for these bizarre JavaScript behaviours mentioned in the 'Wat' talk for CodeMash 2012?

javascript

提问by NibblyPig

The 'Wat' talk for CodeMash 2012basically points out a few bizarre quirks with Ruby and JavaScript.

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

I have made a JSFiddle of the results at http://jsfiddle.net/fe479/9/.

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

The behaviours specific to JavaScript (as I don't know Ruby) are listed below.

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

I found in the JSFiddle that some of my results didn't correspond with those in the video, and I am not sure why. I am, however, curious to know how JavaScript is handling working behind the scenes in each case.

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

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

I am quite curious about the +operator when used with arrays in JavaScript. This matches the video's result.

我很好奇+在 JavaScript 中与数组一起使用时的运算符。这与视频的结果相符。

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

This matches the video's result. What's going on here? Why is this an object. What does the +operator do?

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

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

This doesn't match the video. The video suggests that the result is 0, whereas I get [Object].

这与视频不符。该视频表明结果为 0,而我得到 [Object]。

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

This doesn't match the video either, and how does outputting a variable result in two objects? Maybe my JSFiddle is wrong.

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

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

Doing wat + 1 results in wat1wat1wat1wat1...

做 wat + 1 结果wat1wat1wat1wat1...

I suspect this is just straightforward behaviour that trying to subtract a number from a string results in NaN.

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

回答by Ventero

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.

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

  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 ToNumberon the string "[object Object]". When following the steps from §9.3.1, we get NaNas a result:

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

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

    As per §15.4.1.1and §15.4.2.2, Array(16)creates a new array with length 16. To get the value of the argument to join, §11.6.2steps #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 NaNas per §9.3.1. Following step 7 of §11.6.2, §11.6.3dictates that

    If either operand is NaN, the result is NaN.

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

    If mis 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" + 1instead of "wat" - 1as argument, the addition operator converts 1to a string instead of converting "wat"to a number, so it effectively calls Array(16).join("wat1").

  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]"(参见[] + {})。所以要得到 的结果+{},我们必须对ToNumber字符串应用"[object Object]"。当按照§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 ( §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").

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.

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

回答by CR Drost

This is more of a comment than an answer, but for some reason I can't comment on your question. I wanted to correct your JSFiddle code. However, I posted this on Hacker News and someone suggested that I repost it here.

这与其说是回答,不如说是评论,但出于某种原因,我无法对您的问题发表评论。我想更正您的 JSFiddle 代码。然而,我在 Hacker News 上发布了这个,有人建议我在这里重新发布。

The problem in the JSFiddle code is that ({})(opening braces inside of parentheses) is not the same as {}(opening braces as the start of a line of code). So when you type out({} + [])you are forcing the {}to be something which it is not when you type {} + []. This is part of the overall 'wat'-ness of Javascript.

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

The basic idea was simple JavaScript wanted to allow both of these forms:

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

if (u)
    v;

if (x) {
    y;
    z;
}

To do so, two interpretations were made of the opening brace: 1. it is not requiredand 2. it can appear anywhere.

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

This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere, and real code also tends to be more fragile when it uses the first form rather than the second. (About once every other month at my last job, I'd get called to a coworker's desk when their modifications to my code weren't working, and the problem was that they'd added a line to the "if" without adding curly braces. I eventually just adopted the habit that the curly braces are always required, even when you're only writing one line.)

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

Fortunately in many cases eval() will replicate the full wat-ness of JavaScript. The JSFiddle code should read:

幸运的是,在许多情况下 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>");

[Also that is the first time I have written document.writeln in many many many years, and I feel a little dirty writing anything involving both document.writeln() and eval().]

[也是这么多年以来我第一次写document.writeln,我觉得写任何涉及document.writeln()和eval()的东西都有些脏。]

回答by Axel Rauschmayer

I second @Ventero's solution. If you want to, you can go into more detail as to how +converts its operands.

我第二个@Ventero 的解决方案。如果需要,您可以更详细地了解如何+转换其操作数。

First step (§9.1):convert both operands to primitives (primitive values are undefined, null, booleans, numbers, strings; all other values are objects, including arrays and functions). If an operand is already primitive, you are done. If not, it is an object objand the following steps are performed:

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

  1. Call obj.valueOf(). If it returns a primitive, you are done. Direct instances of Objectand arrays return themselves, so you are not done yet.
  2. Call obj.toString(). If it returns a primitive, you are done. {}and []both return a string, so you are done.
  3. Otherwise, throw a TypeError.
  1. 打电话obj.valueOf()。如果它返回一个原语,你就完成了。Object和数组的直接实例返回自身,所以您还没有完成。
  2. 打电话obj.toString()。如果它返回一个原语,你就完成了。{}并且[]都返回一个字符串,所以你完成了。
  3. 否则,抛出一个TypeError.

For dates, step 1 and 2 are swapped. You can observe the conversion behavior as follows:

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

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

Interaction (Number()first converts to primitive then to number):

交互(Number()首先转换为原始然后转换为数字):

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

Second step (§11.6.1):If one of the operands is a string, the other operand is also converted to string and the result is produced by concatenating two strings. Otherwise, both operands are converted to numbers and the result is produced by adding them.

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

More detailed explanation of the conversion process: “What is {} + {} in JavaScript?

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

回答by Mariusz Nowak

We may refer to the specification and that's great and most accurate, but most of the cases can also be explained in a more comprehensible way with the following statements:

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

  • +and -operators work only with primitive values. More specifically +(addition) works with either strings or numbers, and +(unary) and -(subtraction and unary) works only with numbers.
  • All native functions or operators that expect primitive value as argument, will first convert that argument to desired primitive type. It is done with valueOfor toString, which are available on any object. That's the reason why such functions or operators don't throw errors when invoked on objects.
  • +-运算符仅适用于原始值。更具体地说,+(加法)适用于字符串或数字,而+(一元)和-(减法和一元)仅适用于数字。
  • 所有期望原始值作为参数的本机函数或运算符,将首先将该参数转换为所需的原始类型。它通过valueOf或完成,toString可用于任何对象。这就是为什么这些函数或运算符在对象上调用时不会抛出错误的原因。

So we may say that:

所以我们可以说:

  • [] + []is same as String([]) + String([])which is same as '' + ''. I mentioned above that +(addition) is also valid for numbers, but there is no valid number representation of an array in JavaScript, so addition of strings is used instead.
  • [] + {}is same as String([]) + String({})which is same as '' + '[object Object]'
  • {} + []. This one deserves more explanation (see Ventero answer). In that case, curly braces are treated not as an object but as an empty block, so it turns out to be same as +[]. Unary +works only with numbers, so the implementation tries to get a number out of []. First it tries valueOfwhich in the case of arrays returns the same object, so then it tries the last resort: conversion of a toStringresult to a number. We may write it as +Number(String([]))which is same as +Number('')which is same as +0.
  • Array(16).join("wat" - 1)subtraction -works only with numbers, so it's the same as: Array(16).join(Number("wat") - 1), as "wat"can't be converted to a valid number. We receive NaN, and any arithmetic operation on NaNresults with NaN, so we have: Array(16).join(NaN).
  • [] + []与 相同 与String([]) + String([])相同'' + ''。我在上面提到过+(addition) 也对数字有效,但 JavaScript 中没有数组的有效数字表示,因此使用字符串相加代替。
  • [] + {}相同于String([]) + String({})相同于'' + '[object Object]'
  • {} + []. 这个值得更多解释(见 Ventero 回答)。在这种情况下,花括号不被视为一个对象,而是一个空块,因此结果与+[]. 一元+仅适用于数字,因此实现尝试从[]. 首先它尝试valueOf在数组的情况下哪个返回相同的对象,然后它尝试最后的手段:将toString结果转换为数字。我们可以把它写成+Number(String([]))which is same as +Number('')which is same as +0
  • Array(16).join("wat" - 1)减法-仅适用于数字,因此与: 相同Array(16).join(Number("wat") - 1),因为"wat"不能转换为有效数字。我们接收NaN,以及对NaN结果的任何算术运算NaN,所以我们有:Array(16).join(NaN)

回答by AbdulFattah Popoola

To buttress what has been shared earlier.

支持之前分享的内容。

The underlying cause of this behaviour is partly due to the weakly-typed nature of JavaScript. For example, the expression 1 + “2” is ambiguous since there are two possible interpretations based on the operand types (int, string) and (int int):

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

  • User intends to concatenate two strings, result: “12”
  • User intends to add two numbers, result: 3
  • 用户打算连接两个字符串,结果:“12”
  • 用户打算将两个数字相加,结果:3

Thus with varying input types,the output possibilities increase.

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

The addition algorithm

加法算法

  1. Coerce operands to primitive values
  1. 将操作数强制为原始值

The JavaScript primitives are string, number, null, undefined and boolean (Symbol is coming soon in ES6). Any other value is an object (e.g. arrays, functions and objects). The coercion process for converting objects into primitive values is described thus:

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

  • If a primitive value is returned when object.valueOf() is invoked, then return this value, otherwise continue

  • If a primitive value is returned when object.toString() is invoked, then return this value, otherwise continue

  • Throw a TypeError

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

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

  • 抛出类型错误

Note: For date values, the order is to invoke toString before valueOf.

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

  1. If any operand value is a string, then do a string concatenation

  2. Otherwise, convert both operands to their numeric value and then add these values

  1. 如果任何操作数值为字符串,则进行字符串连接

  2. 否则,将两个操作数都转换为其数值,然后将这些值相加

Knowing the various coercion values of types in JavaScript does help to make the confusing outputs clearer. See the coercion table below

了解 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           |
+-----------------+-------------------+---------------+

It is also good to know that JavaScript's + operator is left-associative as this determines what the output will be cases involving more than one + operation.

知道 JavaScript 的 + 运算符是左关联的也是一件好事,因为这决定了涉及多个 + 操作的情况下的输出。

Leveraging the Thus 1 + "2" will give "12" because any addition involving a string will always default to string concatenation.

利用因此 1 + "2" 将得到 "12",因为任何涉及字符串的加法将始终默认为字符串连接。

You can read more examples in this blog post(disclaimer I wrote it).

您可以在这篇博文中阅读更多示例(免责声明是我写的)。