Those Strange Events about JavaScript You Should Know

As one JavaScript developer, there are some strange events about JavaScript you should know. Maybe, you have never seen those strange events in JavaScript. But in this post, I just want to sort out the confusing expressions in javascript and the principles behind it.

Do you know the result of these expressions?

  • 1 + '1'
  • 1 - '1'
  • '2' + '2' - '2'
  • [] + []
  • {} + {}
  • [] + {}
  • {} + []
  • [] + {} === {} + []
  • {} + [] === [] + {}
  • [+false] + [+false] + [+false]
  • [+false] + [+false] + [+false] - [+false]
  • '1' == true
  • parseInt('infinity') == 0 / 0
  • 1 < 2 < 3
  • 3 > 2 > 1
  • isNaN(false)
  • isNaN(null)
  • [[][[]]+[]][+[]][++[+[]][+[]]]

If you want to know the correct answer, paste the expression into the browser’s console and execute it.

The next step is to explain what the results of these expressions are based on.

The key to solving the above problems is to understand three points:

  1. Operator usage and priority
  2. The operand data type conversion rule in the context of the operator
  3. The special case in grammar

+ operator

+ There are three roles in JavaScript:

  1. Connection string: var result = 'Hello' + 'World'
  2. Calculate the sum of the numbers: var result = 1 + 2
  3. As a unary operator: +variable

In the expression + is the operator (operator), the object of the operator operation ( Hello , World , 1 , 2 in the above example) is called operand (operand)

The rule for the unary + operator is: ToNumber(ToPrimitive(operand)) , which converts any type into a numeric type.

When the data types of the operands are inconsistent, the conversion is based on the following rules:

  • If at least one operand is an object data type ( object ), then you need to convert it to a primitive type ( primitive ), ie string, number or boolean
    1. If the object is a Date type, then call the toString() method
    2. Otherwise, the valueOf() method is called first.
    3. If the valueof() method does not exist or does not return a base type, then call toString()
    4. When an array is converted to a base type, JavaScript uses the join(',')method.
    5. The result of a simple Javascript object {} conversion is [object Object]
  • After conversion, if at least one operand is a string type, then the other operand needs to be converted to a string type and then the join operation is performed.
  • In other cases, both operands are converted to numeric types and the addition is performed
  • If both operands are primitive types, the operator will determine that at least one is a string type and performs a join operation. Everything else is converted to numbers and summed

So according to the above rules, we can explain:

  • The result of 1 + '1' is '11' because one of the operands is a string, so the other operand is also converted to a string and a string concatenation operation is performed.
  • The result of [] + [] is '' an empty string, because the array is an object type, the result of converting to the underlying type is an empty string, which is still an empty string after splicing
  • The result of [] + {} is [object Object] , because the operand has the relationship of the object type, both operands need to be converted to the base type, [] converted to the base type and the result is '' , {}converted to the base The result of the type is [object Object] , and the result of the last string concatenation is still [object Object]

Next, let’s talk about the notable situation.

  • The result of {} + [] is 0 . Because in this expression, the beginning {}not the literal of the empty object, but is treated as an empty block of code.In fact, the value of this expression is the result of +[] , which is Number([].join(',')) , which is 0
  • Even more strange is the expression {} + {} , which will get different results when executed in different browsers. According to the above example, we can similarly introduce the value of this expression is actually the value of +{} , that is, the final result is Number([object Object]) , which is NaN . The result of this implementation in IE 11 is the same, but if you execute it in Chrome, you get the result [object Object][object Object] .

This is because Chrome devtools implicitly adds parentheses () to the expression when executing the code, and the actual execution code is ({} + {}) . If you execute ({} + {}) in IE 11, you get the result of [object Object][object Object]

  • Although we have already made clear that the result of [] + {} is [object Object] and the result of {} + [] is 0 , if we compare them: [] + {} === {} + [] result will be true . Because the relationship of {}following the === on the right side is no longer considered to be an empty code block, but a literal empty object, so the result on both sides is [object Object]
  • {} + [] === [] + {} also an ambiguous result. In theory, the return value of an expression is false , which is false in IE 11, but returns true in Chrome’s devtools. Is the expression being executed in ()
  • [+false] + [+false] + [+false] can also be imagined. The result of +false is false converted to the number 0 , after which [0] is converted to the base type string '0' . So the final result of the expression is '000'

- operator

Although the - operator and the + operator look the same, the - operator has only one function, which is a numerical subtraction. It will try to convert non-numeric operands into numeric types. If the result of the conversion is NaN , then the result of the expression can be imagined as NaN . If all the conversions are successful, the subtraction operation is performed, so

  • 1 - '1' actually performs 1 - 1 and the result is 0
  • '2' + '2' - '2' expressions must first follow the execution order from left to right, '2' + '2' is executed in string concatenation, the result is '22' , in the next '22' - '2' calculation, both operands are successfully converted to numbers, and the result is the result of digital subtraction 20
  • [+false] + [+false] + [+false] - [+false] expression actually executes '000' - '0' and the final result is the number 0

== operator

In JavaScript, === called the identity operator, and == called the equality operator. Because of the length of the relationship, here we simply talk about the topic

If the data type of the operand of the == operator is different:

  1. If one operand is null and the other operand is undefined , they are equal
  2. If one operand is a numeric type and the other is a string type, then convert the string type to a numeric type and compare
  3. If an operand is a boolean type, then convert true to 1, and false to 0 for comparison
  4. If one operand is an object and the other operand is a number or a string, then convert the object to a primitive type and compare
  • According to the above rules, when calculating the expression '1' == true , first convert true to the number 1 , then the value and string type exist in the expression, and then convert the string '1' to the number 1 , and finally 1 == 1 course established
  • The expression parseInt('infinity') == 0 / 0 actually judging NaN == NaN , such a comparison is a special case, whether in the == comparison or ===comparison, NaN will not be equal to anything Or, as long as any operand is NaN , the expression will return false

For a more comprehensive comparison of == and === .

The comparison operators > and < also follow similar rules: 1. Compare strings to numbers for comparison; 2. Convert boolean types to numbers and compare them,

  • In expression 1 < 2 < 3 , first perform 1 < 2 and the result is true , but in the process of comparing true < 3 , you need to convert true to numeric type 1 , and finally compare 1 < 3 , return true
  • Similarly, in the expression 3 > 2 > 1 , the final comparison is actually true > 1 , which means that 1 > 1 course returns false

isNaN

“NaN” is an abbreviation for “Not a Number”. We think that isNaN can be used directly to determine whether a value is a numeric type, but it is not. Because isNaN first forces the parameter to be converted to a numeric type, and then judges. This is not difficult to explain why isNaN(false) and isNaN(null) return true , because both false and null can be successfully converted to the number 0 , so for isNaN , they are numbers

Summary

Finally, we use the expression [[][[]]+[]][+[]][++[+[]][+[]]] as the end of the article.

Three operators appear in this expression, respectively

  • Member operator: []
  • Unary operator: +
  • Operators that act as summation or connection strings: +
  • Self-increment operator: ++

Based on the operator’s prioritization table , we can determine that the operator’s priority is: [] > unary operator + > ++ > +

So according to the priority we can first calculate the +[] part of the expression and replace this part of the expression with the result of the calculation: [[][[]]+[]][0][++[0][0]]

Next we split the expression into three parts: [ [][[]]+[] ] [0] [ ++[0][0] ] . If it is still unclear, the three parts are from left to right:

  1. [ [][[]]+[] ]
  2. [0]
  3. [ ++[0][0] ]

Let’s look at the [][[]] operands in the first part of the first part, the first [] is an empty array, and the immediately following [[]] is the attribute accessor (member operator), within the attribute accessor. The [] will be cast to a string type, and the final result is an empty string '' , so the final result of the first operand is actually [][''] , which is undefined , and because of the + operation The rule of the character, the result of the final [][[]]+[] expression is the string 'undefined' , then the result of the current stage expression is ['undefined'][0][++[0][0]] , ie 'undefined'[++[0][0]]

Next we solve the third part: [++[0][0]] , I already know that the member operator [] a higher priority than the auto increment operator ++ , so about the expression ++[0][0] , we need to calculate [0][0] , the result is 0 , then the result of calculating ++0 is 1

So the final expression is converted to 'undefined'[1] and the final result is 'n'

Reference article