Original: http://www.2ality.com/2012/01/object-plus-object.html
Gary Bernhardt recently pointed out an interesting JavaScript quirk in a short lecture video "Wat" : When you add objects and arrays together, you get some unexpected results. This article will explain in turn how the results of these calculations are derived.
In JavaScript, the rules of addition are simple: you can only add numbers and numbers, or strings and strings, and all other types of values are automatically converted to these two types of values. In order to figure out how this implicit conversion works, we first need to understand some basic knowledge. Note: In the following article, when referring to a section (such as §9.1), it refers to the chapters in the ECMA-262 Language specification (ECMAScript 5.1).
Let's review it quickly. In JavaScript, there are two types of values: the original value (primitives) and the object value (objects). The original values are:undefined, null, Boolean ( Booleans), Number (numbers), and string (strings). All other values are values of the object type, including arrays (arrays) and functions (functions).
1. Type conversion
The addition operator triggers three types of conversions: converting the value to the original value, converting it to a number, and converting it to a string, which corresponds to three abstract operations within the JavaScript Engine: Toprimitive (), Tonumber (), ToString ()
1.1 Convert a value to the original value by Toprimitive ()
The abstract operation inside the JavaScript engine toprimitive () has such a signature:
Toprimitive (input, Preferredtype?)
The optional parameter Preferredtype can be number or String, which represents only a preference for a transformation, and the result must not necessarily be the type that the parameter refers to. But the conversion result must be a primitive value. If Preferredtype is marked as number, the following action is taken to convert the input value (§9.1):
- If the value entered is already a raw value, it is returned directly.
- Otherwise, if the value entered is an object. The valueOf () method of the object is called. If the return value of the valueOf () method is a raw value, the original value is returned.
- Otherwise, the ToString () method of this object is called. If the return value of the toString () method is a raw value, the original value is returned.
- Otherwise, the TypeError exception is thrown.
If Preferredtype is marked as String, the second and third steps of the conversion operation are reversed. If you do not have preferredtype this parameter, the value of Preferredtype is automatically set according to the following rules:theDate type object is set to string, and the value of the other type is set to number .
1.2 Convert a value to a number by Tonumber ()
The table below explains how Tonumber () converts the original value to a number (§9.3).
Parameters |
Results |
Undefined |
NaN |
Null |
+0 |
Boolean value |
true is converted to 1,false converted to +0 |
Digital |
No conversion required |
String |
Parsed by a string to a number. For example, "324" is converted to 324 |
If the value entered is an object, then toprimitive (obj, number) is called first to convert the object to the original value, and thenthe original value is converted to a digit by calling T Onumber ().
1.3 Converting a value to a string by ToString ()
The following table explains how ToString() converts the original value to a string (§9.8).
Parameters |
Results |
Undefined |
"Undefined" |
Null |
"NULL" |
Boolean value |
"true" or "false" |
Digital |
Numbers as strings, for example. "1.765" |
String |
No conversion required |
If you enter a value that is an object, you first call toprimitive (obj, string) to convert the object to the original value, and then call ToString() to convert the original value to a string.
1.4 Practice a bit
The following object allows you to see the conversion process inside the engine.
var obj = { valueof:function () { console.log ("valueOf"); return {}; No return original value }, tostring:function () { console.log ("toString"); return {}; No original value returned }}
When number is called as a function (rather than as a constructor call), the Tonumber () operation is called inside the engine:
2. Addition
There is an addition operation like the following.
Value1 + value2
In evaluating this expression, the internal steps are as follows (§11.6.1):
- Convert two operands to the original value (the following is the mathematical notation, not the JavaScript code):
PRIM1: = toprimitive (value1)
PRIM2: = toprimitive (value2)
Preferredtype is omitted, so the value of the date type takes string, and the value of the other type takes number.
- If either prim1 or prim2 is a string, the other is converted to a string, and then the result of the two string connection operation is returned .
- Otherwise, convert both prim1 and prim2 to numeric types, returning their and .
2.1 Expected results
When two empty arrays are added, the result is what we expected:
> [] + []
[] will be converted to a primitive value, first try the ValueOf () method returns the array itself (This):
> var arr = [];> arr.valueof () = = = Arrtrue
The result is not the original value, so call the toString () method and return an empty string (which is an original value). Therefore, theresult of [] + [] is actually a connection of two empty strings .
Add an empty array and an empty object, and the result also matches our expectations:
> [] + {} ' [Object Object] '
Similarly, an empty object is converted to a string.
> String ({}) ' [Object Object] '
So the end result is a connection of " " and "[Object Object]" two strings .
Here are more examples of objects converted to raw values, can you understand:
> 5 + new Number (7) 12> 6 + {valueof:function () {return 2}}8> "abc" + {tostring:function () {return "Def" }} ' ABCdef '
2.1 Unexpected results
If the first operand in front of the plus sign is an empty object literal, the result is unexpected (the following code runs in the Firefox console):
> {} + {}nan
What's the matter? The reason is that the JavaScript engine interprets the first {} as an empty block of code and ignores it. Nan is actually the following expression +{} Results of the calculation (Plus and later {}). The plus sign here is not a two-dollar operator that represents addition, but rather a unary operator that converts the operands after it to a number, exactly as the number () function . For example:
> + "3.65" 3.65
The steps for conversion are this:
+{}number ({}) Number ({}.tostring ()) ///Because {}.valueof () is not the original ("[Object Object]") NaN
Why is the first {} parsed into a block of code ? The reason is that the entire input is parsed into a statement, and if a statement begins with an opening brace, the brace is parsed into a block of code. So, you can also fix the result by forcing the input to parse into an expression. :
> ({} + {}) ' [Object Object][object Object] '
In addition, the parameters of a function or method are parsed into an expression:
> Console.log ({} + {}) [Object Object][object Object]
You should not be surprised at the results of these calculations, as described in the previous tutorial:
> {} + []0
Once explained, the above input is parsed into a block of code followed by an expression +[]. The steps to convert are:
+[]number ([]) number ([].tostring ()) ///Because [].valueof () is not the original value # ("") 0
Interestingly, the repl of node. JS has different parsing results than Firefox and Chrome (and node. JS uses the V8 engine) when parsing similar inputs. The following input will be parsed into an expression, and the result is more in line with our expectations:
> {} + {} ' [Object Object][object object] ' > {} + [] ' [Object Object] '
The following is a comparison of the results in SpiderMonkey and Nodejs.
3. Other
In most cases, it's not difficult to figure out how the + sign in JavaScript works: You can add numbers and numbers together or strings and strings. The object value is converted to the original value before being evaluated. If you want to connect multiple arrays, You need to use the Concat method of the array:
> [1, 2].concat ([3, 4]) [1, 2, 3, 4]
There are no built-in methods in JavaScript to "connect" (merge) multiple objects. You can use a JavaScript library, such as underscore:
> var O1 = {eeny:1, meeny:2};> var O2 = {miny:3, moe:4};> _.extend (O1, O2) {eeny:1, meeny:2 , miny:3,< C6/>moe:4}
Note: Unlike the Array.prototype.concat () method , theextend () method modifies its first argument instead of returning the merged object :
> o1{eeny:1, meeny:2, miny:3, moe:4}> o2{miny:3, Moe:4}
If you want more interesting knowledge about operators, you can read "Fake operator overloading in JavaScript" (wall).
4. Reference
- JavaScript Values:not Everything is an object
Translation In JavaScript, {}+{} equals how much?