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。
以下是对您所看到的结果(以及应该看到的结果)的解释列表。我使用的参考资料来自ECMA-262标准。
[] + []
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.
[] + {}
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).
{} + []
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.
{} + {}
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 ToNumber on the string "[object Object]". When following the steps from §9.3.1, we get NaN as a result:
If the grammar cannot interpret the String as an expansion of StringNumericLiteral, then the result of ToNumber is NaN.
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. ToNumber(1) is simply 1 (§9.3), whereas ToNumber("wat") again is NaN as per §9.3.1. Following step 7 of §11.6.2, §11.6.3 dictates that
If either operand is NaN, the result is NaN.
So the argument to Array(16).join is NaN. Following §15.4.4.5 (Array.prototype.join), we have to call ToString on the argument, which is "NaN" (§9.8.1):
If m is 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" + 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").
至于为什么你会看到{}+[]情况下的不同结果:当使用它作为函数参数时,你强迫语句是一个ExpressionStatement,这使得{}不可能被解析为空块,所以它被解析为一个空对象文字。
以下是对您所看到的结果(以及应该看到的结果)的解释列表。我使用的参考资料来自ECMA-262标准。
[] + []
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.
[] + {}
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).
{} + []
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.
{} + {}
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 ToNumber on the string "[object Object]". When following the steps from §9.3.1, we get NaN as a result:
If the grammar cannot interpret the String as an expansion of StringNumericLiteral, then the result of ToNumber is NaN.
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. ToNumber(1) is simply 1 (§9.3), whereas ToNumber("wat") again is NaN as per §9.3.1. Following step 7 of §11.6.2, §11.6.3 dictates that
If either operand is NaN, the result is NaN.
So the argument to Array(16).join is NaN. Following §15.4.4.5 (Array.prototype.join), we have to call ToString on the argument, which is "NaN" (§9.8.1):
If m is 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" + 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").
至于为什么你会看到{}+[]情况下的不同结果:当使用它作为函数参数时,你强迫语句是一个ExpressionStatement,这使得{}不可能被解析为空块,所以它被解析为一个空对象文字。
我赞同ventero的解决方案。如果您愿意,您可以更详细地了解+如何转换其操作数。
第一步(§9.1):将两个操作数转换为原语(原语值为未定义、空值、布尔值、数字、字符串;所有其他值都是对象,包括数组和函数)。如果操作数已经是原始的,那么就完成了。如果不是,则为对象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
第二步(§11.6.1):如果其中一个操作数是字符串,另一个操作数也转换为字符串,并通过连接两个字符串产生结果。否则,两个操作数都被转换为数字,结果是将它们相加。
转换过程的更详细解释:“在JavaScript中{}+{}是什么?”
这更像是一个评论而不是回答,但出于某种原因,我不能对你的问题发表评论。我想更正你的JSFiddle代码。然而,我在黑客新闻上发布了这篇文章,有人建议我在这里转发。
JSFiddle代码中的问题是({})(圆括号内的开大括号)与{}(开大括号作为代码行的开始)不一样。所以当你输入({}+[])时,你强迫{}成为你输入{}+[]时不是的东西。这是Javascript整体“wat -ness”的一部分。
基本的想法很简单,JavaScript想要同时支持这两种形式:
if (u)
v;
if (x) {
y;
z;
}
为了做到这一点,对开头的大括号有两种解释:它不是必需的。它可以出现在任何地方。
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.)
幸运的是,在许多情况下,eval()将复制JavaScript的全部瓦特性。JSFiddle代码应该如下:
function out(code) {
function format(x) {
return typeof x === "string" ?
JSON.stringify(x) : x;
}
document.writeln('>>> ' + 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()和eval()的东西都有点脏。]
我们可以参考规范,这很好,也很准确,但大多数情况下也可以用以下语句以更容易理解的方式解释:
+和-运算符仅适用于基元值。更具体地说,+(加法)适用于字符串或数字,+(一元)和-(减法和一元)仅适用于数字。
所有希望将原语值作为实参的本机函数或操作符都将首先将该实参转换为所需的原语类型。它是通过valueOf或toString完成的,这两个值在任何对象上都可用。这就是为什么在对象上调用此类函数或操作符时不会抛出错误的原因。
所以我们可以说:
[] + [] 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 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. 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 NaN results with NaN, so we have: Array(16).join(NaN).
来支持之前分享的内容。
这种行为的潜在原因部分是由于JavaScript的弱类型特性。例如,表达式1 + " 2 "是模糊的,因为基于操作数类型(int, string)和(int int)有两种可能的解释:
用户想连接两个字符串,结果:" 12 "
用户想要添加两个数字,结果:3
因此,随着输入类型的变化,输出的可能性增加了。
加法算法
强制操作数为基本值
JavaScript原语是string, number, null, undefined和boolean (Symbol很快会在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",因为任何涉及字符串的加法将始终默认为字符串连接。
你可以在这篇博文中阅读更多的例子(免责声明是我写的)。