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

本文翻译自:What is the explanation for these bizarre JavaScript behaviours mentioned in the 'Wat' talk for CodeMash 2012?

The 'Wat' talk for CodeMash 2012 basically 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. 我在JSFiddle中发现我的某些结果与视频中的结果不符,我不确定为什么。 I am, however, curious to know how JavaScript is handling working behind the scenes in each case. 但是,我很想知道JavaScript在每种情况下是如何在后台进行处理的。

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

I am quite curious about the + operator when used with arrays in JavaScript. 当与JavaScript中的数组一起使用时,我对+运算符感到非常好奇。 This matches the video's result. 这与视频结果匹配。

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。


#1楼

参考:https://stackoom.com/question/btrE/在CodeMash-的-Wat-演讲中提到的这些怪异JavaScript行为的解释是什么


#2楼

To buttress what has been shared earlier. 为了支持先前共享的内容。

The underlying cause of this behaviour is partly due to the weakly-typed nature of JavaScript. 此行为的根本原因部分是由于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): 例如,表达式1 +“ 2”是模棱两可的,因为基于操作数类型(int,string)和(int int)有两种可能的解释:

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

Thus with varying input types,the output possibilities increase. 因此,随着输入类型的变化,输出的可能性也会增加。

The addition algorithm 加法算法

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

The JavaScript primitives are string, number, null, undefined and boolean (Symbol is coming soon in ES6). JavaScript原语是字符串,数字,null,未定义和布尔值(Symbol在ES6中即将推出)。 Any other value is an object (eg arrays, functions and objects). 其他任何值都是一个对象(例如,数组,函数和对象)。 The coercion process for converting objects into primitive values is described thus: 因此描述了将对象转换为原始值的强制过程:

  • If a primitive value is returned when object.valueOf() is invoked, then return this value, otherwise continue 如果在调用object.valueOf()时返回了原始值,则返回该值,否则继续

  • If a primitive value is returned when object.toString() is invoked, then return this value, otherwise continue 如果在调用object.toString()时返回了原始值,则返回该值,否则继续

  • Throw a TypeError 引发TypeError

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 否则,将两个操作数都转换为它们的数值,然后将这些值相加

Knowing the various coercion values of types in JavaScript does help to make the confusing outputs clearer. 了解JavaScript中各种类型的强制值确实有助于使混乱的输出更加清晰。 See the coercion table below 参见下面的强制表

+-----------------+-------------------+---------------+
| 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). 您可以在此博客文章中阅读更多示例(我写过免责声明)。


#3楼

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 ). 使用加法运算符时,左操作数和右操作数都首先转换为基元(第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 ). 按照§9.1的规定 ,将对象(在本例中为数组)转换为基元将返回其默认值,对于具有有效toString()方法的对象,这是调用object.toString()的结果(第8.12.8节 )。 For arrays this is the same as calling array.join() ( §15.4.4.2 ). 对于数组,这与调用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. 连接一个空数组会导致一个空字符串,因此加法运算符的第7步将返回两个空字符串的并置,即空字符串。

  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 ). 对于“对象对象”(第15.2节),这再次是调用object.toString()的结果,对于非空,未定义的对象,它是"[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). 这里的{}不会被解析为一个对象,而是被解析为一个空块(第§12.1节 ,至少只要您不强迫该语句为表达式,但稍后再介绍)。 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)) . 一元+运算符(第11.4.6节 )返回ToNumber(ToPrimitive(operand)) As we already know, ToPrimitive([]) is the empty string, and according to §9.3.1 , ToNumber("") is 0. 我们已经知道, ToPrimitive([])是空字符串,根据§9.3.1ToNumber("")为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 [] + {} ). 同样, +{}ToNumber(ToPrimitive({})) ,而ToPrimitive({})"[object Object]" (请参阅[] + {} )。 So to get the result of +{} , we have to apply ToNumber on the string "[object Object]" . 因此,要获得+{}的结果,我们必须将ToNumber应用于字符串"[object Object]" When following the steps from §9.3.1 , we get NaN as a result: 按照§9.3.1的步骤进行操作时 ,我们将得到NaN

    If the grammar cannot interpret the String as an expansion of StringNumericLiteral , then the result of ToNumber is NaN . 如果语法无法将String解释为StringNumericLiteral的扩展,则ToNumber的结果为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 . 根据§15.4.1.1§15.4.2.2Array(16)创建一个长度为16的新数组。要获取要连接的参数的值, §11.6.2步骤#5和#6表明我们必须进行转换使用ToNumber两个操作数都转换为数字。 ToNumber(1) is simply 1 ( §9.3 ), whereas ToNumber("wat") again is NaN as per §9.3.1 . ToNumber(1)仅仅是1( 第9.3节 ),而ToNumber("wat")再次是NaN§9.3.1 Following step 7 of §11.6.2 , §11.6.3 dictates that 遵循§11.6.2的步骤7, §11.6.3规定:

    If either operand is NaN , the result is NaN . 如果任一操作数为NaN ,则结果为NaN

    So the argument to Array(16).join is NaN . 因此Array(16).join的参数为NaN Following §15.4.4.5 ( Array.prototype.join ), we have to call ToString on the argument, which is "NaN" ( §9.8.1 ): 继§15.4.4.5( Array.prototype.join ),我们要调用ToString的说法,这是"NaN"§9.8.1 ):

    If m is NaN , return the String "NaN" . 如果mNaN ,则返回字符串"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. §15.4.4.5的第10步之后,我们获得了"NaN"和空字符串的串联的15次重复,等于您看到的结果。 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") . 当使用"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 ,这使得无法将{}解析为空块,因此而是将其解析为空对象文字。


#4楼

I second @Ventero's solution. 我第二个@Ventero的解决方案。 If you want to, you can go into more detail as to how + converts its operands. 如果愿意,可以更详细地了解+如何转换其操作数。

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). 第一步(第9.1节):将两个操作数都转换为基元(基元值是undefinednull ,booleans,数字,字符串;所有其他值都是对象,包括数组和函数)。 If an operand is already primitive, you are done. 如果操作数已经是原始的,则操作完成。 If not, it is an object obj and the following steps are performed: 如果不是,则为对象obj然后执行以下步骤:

  1. Call obj.valueOf() . 调用obj.valueOf() If it returns a primitive, you are done. 如果返回原语,则操作完成。 Direct instances of Object and arrays return themselves, so you are not done yet. Object和数组的直接实例将返回自身,因此您尚未完成。
  2. Call obj.toString() . 调用obj.toString() If it returns a primitive, you are done. 如果返回原语,则操作完成。 {} and [] both return a string, so you are done. {}[]都返回一个字符串,因此您已完成。
  3. Otherwise, throw a TypeError . 否则,抛出TypeError

For dates, step 1 and 2 are swapped. 对于日期,将交换步骤1和2。 You can observe the conversion behavior as follows: 您可以观察到转换行为,如下所示:

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):

> 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. 第二步(第11.6.1节):如果一个操作数是一个字符串,则另一个操作数也将转换为字符串,并通过串联两个字符串来产生结果。 Otherwise, both operands are converted to numbers and the result is produced by adding them. 否则,两个操作数都将转换为数字,并通过将它们相加来产生结果。

More detailed explanation of the conversion process: “ What is {} + {} in JavaScript? 转换过程的详细说明:“ JavaScript中的{} + {}是什么?


#5楼

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 valueOf or toString , which are available on any object. 它是通过valueOftoString ,它们在任何对象上都可用。 That's the reason why such functions or operators don't throw errors when invoked on objects. 这就是为什么此类函数或运算符在对象上调用时不会引发错误的原因。

So we may say that: 所以我们可以这样说:

  • [] + [] is same as String([]) + String([]) which is same as '' + '' . [] + []String([]) + String([])相同,与'' + ''相同。 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. 我在上面提到过, + (加号)对数字也有效,但是JavaScript中没有有效的数组数字表示形式,因此使用字符串加法来代替。
  • [] + {} is same as String([]) + String({}) which is same as '' + '[object Object]' [] + {}String([]) + String({})相同,与'' + '[object Object]'
  • {} + [] . {} + [] This one deserves more explanation (see Ventero answer). 这个值得更多解释(请参阅Ventero答案)。 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 valueOf which in the case of arrays returns the same object, so then it tries the last resort: conversion of a toString result to a number. 首先,它尝试使用valueOf ,在数组的情况下,该方法返回相同的对象,因此,它尝试了最后的手段:将toString结果转换为数字。 We may write it as +Number(String([])) which is same as +Number('') which is same as +0 . 我们可以将其写为+Number(String([])) ,与+Number('')相同,与+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. Array(16).join("wat" - 1)减法-仅适用于数字,因此与Array(16).join(Number("wat") - 1) ,因为"wat"不能转换为有效数字。 We receive NaN , and any arithmetic operation on NaN results with NaN , so we have: Array(16).join(NaN) . 我们收到NaN ,并且使用NaNNaN结果进行任何算术运算,因此得到: Array(16).join(NaN)

#6楼

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. 我想更正您的JSFiddle代码。 However, I posted this on Hacker News and someone suggested that I repost it here. 但是,我将此内容发布在了《黑客新闻》上,有人建议我在此处重新发布。

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). JSFiddle代码中的问题是({}) (括号内的大括号)与{} (代码行的开头的大括号)不同。 So when you type out({} + []) you are forcing the {} to be something which it is not when you type {} + [] . 因此,当您键入out({} + [])您将强制{}成为键入{} + []时不是的东西。 This is part of the overall 'wat'-ness of Javascript. 这是Java整体“浪费”的一部分。

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 required and 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”中添加了一行而没有添加卷曲我最终只是养成了始终需要大括号的习惯,即使您只写一行也是如此。

Fortunately in many cases eval() will replicate the full wat-ness of JavaScript. 幸运的是,在许多情况下,eval()可以复制JavaScript的全部内容。 The JSFiddle code should read: 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()的任何东西我感到有点脏。]

发布了0 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/asdfgh0077/article/details/105449331
今日推荐