16

Whether in technical chat groups or forums or in interviewers’ papers, you can always encounter [] + {} == ? such as 060dd8f989cc12. If you don’t understand the principle, then you can’t get in and you can only wait for the big guys to answer. Up.

Type

After all, it is still a problem of JS type conversion. First, let's review the 8 built-in types of JS:

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Object
  • Symbol (ES2015)
  • BigInt (ESNext stage 4)

Does it feel that there is still a Function, after all, it can be typeof ? No, functions and arrays are all subtypes of Object.

Types are divided into types: 160dd8f989cd1f basic type and composite type. Except for objects, the others are all basic types.

To Primitive

Pronunciation: [ˈprɪmətɪv]
Structure: toPrimitive(input: any , preferedType?: ' |'number' )
Function: internal method, convert any value into original value

Conversion rules:

  1. If it is a basic type, it will not be processed.
  2. Call valueOf() and make sure that the return value is of the basic type.
  3. If there is no valueOf method or the type returned by valueOf is not a basic type, the object will continue to call the toString() method.
  4. If there are no valueOf and toString methods at the same time, or if the returned type is not a basic type, then the TypeError exception will be thrown directly.
Note: If preferedType=string , then the order of 2, 3 is reversed.

Next, we look at the conversion realization of each object

ObjectvalueOf()toString()Default preferredType
ObjectOriginal value"[object Object]"Number
FunctionOriginal value"function xyz() {...}"Number
ArrayOriginal value"x,y,z"Number
Datedigital"Sat May 22 2021..."String
  1. The toString() of the array can be equivalent to join(",") . It will be ignored when it encounters null and undefined. It will report an error directly when it encounters a symbol, and it will also report an error when it encounters an object that cannot be ToPrimitive.
  2. When using template string or String(...) packaging, preferredType=string, which means calling .toString() first.
  3. When using subtraction or Number(...) packaging, preferredType=number, which means calling .valueOf()
[1, null, undefined, 2].toString() === '1,,,2';

// Uncaught TypeError: Cannot convert a Symbol value to a string
[1, Symbol('x')].toString()

// Uncaught TypeError: Cannot convert object to primitive value
[1, Object.create(null)].toString()

ToNumber

Some examples of converting special values to numbers will be used later

Number("0") === 0;
Number("") === 0;
Number("   ") === 0;
Number("\n") === 0;
Number("\t") === 0;
Number(null) === 0;
Number(false) === 0;

Number(true) === 1;

Number(undefined); // NaN
Number("x"); // NaN

Addition and subtraction +-

Some implicit conversion rules are followed in addition and subtraction operations:

When encountering an object, perform ToPrimitive conversion to a basic type
  • addition (+) operation, preferredType is the default value
  • subtraction (-) operation, preferredType is Number
// {}.toString() === "[object Object]"
1 + {} === "1[object Object]"

// [2, 3].toString() === "2,3"
1 + [2, 3] === "12,3"
[1] + [2, 3] === "1,2,3"

function test() {}
// test.toString() === "function test() {}"
10 + test === "10function test() {}"
String + any value, will be processed as a string of splicing

Any value here refers to the basic type, because the object will first execute ToPrimitive to become the basic type

1 + "1" === "11"
1 + 1 === 2
1 + 1 + "1" === "21"
"1" + 1 === "11"
"1" + "1" === "11"
1 + "1" + 1 === "111"
"1" + false === "1false"
"1" + null === "1null"
"X" + undefined + false === "Xundefinedfalse"
Non-string + non-string, both sides will be ToNumber first

The non-string here refers to the basic type, because the object will first execute ToPrimitive to become the basic type

1 + true === 2
1 + false === 1
1 + null === 1
1 + null + false + 1 === 2
1 + undefined // NaN
1 + undefined + false // NaN
1 + undefined + [1] === "NaN1"
1 + undefined + "1" === "NaN1"
null + null === 0

// 1 + false
1 + ![] === 1
1 + !{} === 1
!{} + !{} === 0
Arbitrary value-For any value, ToNumber is always executed for numerical calculation.

At this time preferredType === number

3 - 1 === 2
3 - '1' === 2
'3' - 1 === 2
'3' - '1' - '2' === 0

// [].toString() => "" => Number(...) => 0
3 - [] === 3

// {}.toString() => "[object Object]" => Number(...) => NaN
3 - {} // NaN

// Date的默认preferedType === string
var date = new Date();
date.toString = () => 'str';
date.valueOf = () => 123;

date + 1 === 'str1';
date - 1 = 122;
+ x and unary operation + x are equivalent (and-x), both will force ToNumber
+ 0 === 0
- 0 === -0

+ new Date() === 1623251729456


// 1 + (+"1")
1 + + "1" === 2

// 1 + (+(+(+["1"])))
1 + + + + ["1"] === 2

// 1 + (-(+(-[1])))
1 + - + - [1] === 2

// 1 - (+(-(+1)))
1 - + - + 1 === 2

1 - + - + - 1 === 0

// 1 + [""]
1 + + [""] === 1

// ["1", "2"].toString() => "1,2" => Number(...) => NaN
1 + + ["1", "2"] // NaN

// 吃根香蕉🍌
// "ba" + (+undefined) + "a" => "ba" + NaN + "a"
("ba" + + undefined + "a").toLowerCase() === "banana"

[] + {} back to the question 060dd8f989d0fb that was raised at the beginning, is this too simple?

[].toString() === "";
{}.toString() === "[object Object]";

[] + {} === "[object Object]";
{} may no longer be an object when at the forefront

What is not the object? Is it your eight pack abs? Don't worry, look at the classic example

{} + [] === 0;
{ a: 2 } + [] === 0;

What's this stuff? What about "[object Object]"?

Well, this is {} actually represents the code block , and finally becomes + [] . According to the previous principle, the array is first converted to the string "" , and then because of the +x operation, the string is converted to the number 0 .

Then {a: 2} should always be the object, right? In fact, a does not represent object properties at this time, but is regarded as a label. The label is already available in IE6. So if we write an object, it will report an error, and the comma must be changed to the number to pass the compilation.

// Uncaught SyntaxError: Unexpected token ':'
{ a: 2, b: 3 } + []

// 分号OK
{ a: 2; b: 3 } + [] === 0;

⚠️Note: In the version of >= 13.10.0 {} is first interpreted as an empty object, and only in the case of a non-object structure will it be considered as a code block.

// nodeJs >= 13.10.0 的运行结果

{} + [] === "[object Object]";
{ a: 2 } + [] === "[object Object]";
{ a: 2, b: 3 } + [] === "[object Object]";

// 注意是分号,当成代码块
{ a: 2; b: 3 } + [] === 0;
// 有JS语句或者表达式,当成代码块
{ var a = 1; } + [] === 0;
{ ; } + [] === 0;
{ 123 } + [] === 0;
{ 1 + 2 } + [] === 0;

The conclusion is still too early, we still have a way to let the engine prioritize processing into code blocks

// 所有node版本

;{} + [] === 0;
;{ a: 2 } + [] === 0;

// Uncaught SyntaxError: Unexpected token ':'
;{ a: 2, b: 3 } + [];

Add a semicolon? interesting.


Before using this exciting rule, you need to remember that the code block {} must be the first , otherwise it is an object, but there are special cases (semicolons).

console.log({} + []); // "[object Object]"
alert({} + []); // "[object Object]"

function test(x) { console.log(x); }
test({} + []); // "[object Object]"

var result = {} + [];
console.log(result); // "[object Object]"

({}) + [] === "[object Object]"

!{} // !对象 => false

{} // 对象
{ a: 2 } // 对象
{ a: 2, b: 3 } // 对象

{ a: 2; b: 3 } // 代码块
;{ a: 2 } // 代码块
{ a: 2 }; // 代码块
symbol cannot be added or subtracted

If there is a symbol type in the expression, an error will be reported directly. For example, 1 + Symbol("x") following error:

Uncaught TypeError: Cannot convert a Symbol value to a number

Loose equal ==

Equal to congruent requires the type to be judged. When the types are inconsistent, loose equality will trigger an implicit conversion. The rules are described below:

Object == object, no conversion is done if the type is the same
{} != {}
[] != {}
[] != []
Object == basic value, the object first performs ToPrimitive conversion to the basic type
// 小心代码块
"[object Object]" == {}
[] == ""
[1] == "1"
[1,2] == "1,2"
Boolean value == non-Boolean value, the Boolean value is first converted into a number, and then operate according to the number rules
// [] => "" => Number(...) => 0
// false => 0
[] == false

// [1] => "1" => 1
// true => 1
[1] == true

// [1,2] => "1,2" => NaN
// true => 1
[1,2] != true

"0" == false
"" == false
Number == string, string ToNumber is converted to number
"2" == 2
[] == 0
[1] == 1
// [1,2].toString() => "1,2" => Number(...) => NaN
[1,2] != 1
null、undefined、symbol

The result of comparing null, undefined and any non-self value is false, but null == undefined is a special case.

null == null
undefined == undefined
null == undefined

null != 0
null != false

undefined != 0
undefined != false

Symbol('x') != Symbol('x')

Contrast<>

Contrast is not like equality. Strict equality (===) can prevent type conversion, and there must be implicit type conversion in comparison.

The object always executes ToPrimitive first as the basic type
[] < [] // false
[] <= {} // true

{} < {} // false
{} <= {} // true
Any non-string value on any side will be converted to a number for comparison
// ["06"] => "06" => 6
["06"] < 2   // false 

["06"] < "2" // true
["06"] > 2   // true

5 > null     // true
-1 < null    // true
0 <= null    // true

0 <= false   // true
0 < false    // false

// undefined => Number(...) => NaN
5 > undefined // false

To Boolean

Since it is a summary, then we may also talk about the implicit conversion of Boolean values. This is still relatively common, let's see where it will be used:

  • if(...)
  • for(;...;)
  • while(...)
  • do while(...)
  • ... ? :
  • ||
  • &&

Now that you know it will be converted, what value is true and what value is false? To put it another way, all false values are true values. See which ones are false values :

  • undefined
  • null
  • false
  • +0
  • -0
  • NaN
  • ""

Are there any more?

to sum up

Objects need to be converted to basic types from ToPrimitive first, unless the two objects are compared when they are loosely equal (==).

  • + Convert all numbers without a string
  • -Fully converted to numbers, preferredType===Number
  • == No transfer of the same type, number priority, Boolean all transfer to number, null, undefined, symbol not transfer
  • <> Numbers take precedence, unless both sides are strings

Welcome to correct mistakes, welcome to learn and exchange collections. I am original sin, a geek.


夜葬
3.6k 声望1.2k 粉丝

极客