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.
I have made a JSFiddle of the results at http://jsfiddle.net/fe479/9/.
The behaviours specific to JavaScript (as I don't know Ruby) are listed below.
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.
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.
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].
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.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Doing wat + 1 results in wat1wat1wat1wat1
...
I suspect this is just straightforward behaviour that trying to subtract a number from a string results in NaN.
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.
[] + []
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")
.
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.
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.
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.
The basic idea was simple JavaScript wanted to allow both of these forms:
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.
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.)
Fortunately in many cases eval() will replicate the full wat-ness of JavaScript. The JSFiddle code should read:
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>");
[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().]
I second @Ventero's solution. 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). If an operand is already primitive, you are done. If not, it is an object obj
and the following steps are performed:
obj.valueOf()
. If it returns a primitive, you are done. Direct instances of Object
and arrays return themselves, so you are not done yet. obj.toString()
. If it returns a primitive, you are done. {}
and []
both return a string, so you are done. TypeError
. For dates, step 1 and 2 are swapped. 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(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.
More detailed explanation of the conversion process: “What is {} + {} in JavaScript?”
链接地址: http://www.djcxy.com/p/59558.html上一篇: 从相对路径和/或文件名解析绝对路径