Before we start this article, let’s take a look at JavaScript’s amazing implicit conversions:

0 + '1'= = ='01'            // true
true + true= = =2           // true
false= = =0                 // false
false + false= = =0         // true= = = {} + []0               // true[] + {} = = =0               // false

Copy the code

I’m sure you’ve seen a lot of weird examples in various technical communities and daily work, so I won’t go into more details here, but if you can fully understand the above implicit transformation process, you can basically click on the X in the upper right corner.

This article aims to sort out the data types in JS and their corresponding transformation relations. From this article, you can learn:

  • In-depth understanding of JavaScript base types and reference types
  • JavaScript implicit conversion internals
  • Show your strength when talking about relevant topics in the community

JavaScript’s type system

To make clear the implicit conversion, it is inevitable to talk about the type, JS according to the big category there are two types, respectively is the basic type and Object, said to this may have a small partner will question, clearly there are Array, Date… In essence, other advanced types in JS are subtypes of Object. In the follow-up of this paper, Array, Date and other types are collectively referred to as Object types.

JS includes symbol, null, undefined, number, string, Boolean. With Object, there are seven built-in JS types.

In general, we can use the typeof operator to determine built-in types:

typeof Symbol() = = ='symbol'          // true
typeof undefined= = ='undefined'      // true
typeof true= = ='boolean'             // true
typeof 42= = ='number'                // true
typeof The '42'= = ='string'              // true
typeof { bar: 42} = = ='object'       // true

// There is one exception
typeof null= = ='object'              // true
// This bug is due to the underlying implementation of Typeof, which is related to the underlying representation of NULL
Copy the code

Distinguish Object subtypes

Since the typeof indistinguishable Array and Date, that we how to distinguish the Object subtype, implement these subtypes in JS for they added a internal properties [[Class], we can through the Object. The prototype. The toString () to look at it.

Object.prototype.toString.call(/i/g)         // "[object RegExp]"
Object.prototype.toString.call(Date.now())   // "[object Date]"
Copy the code

It is important to note the Object. The prototype. ToString should only used to distinguish is determine the type of the Object:

var num = 42
var numObj = new Number(42)
typeof num    // number
typeof numObj // object
Object.prototype.toString.call(num)         // "[object Number]"
Object.prototype.toString.call(numObj)      // "[object Number]"
/ / you can see the Object. The prototype. ToString is not good to distinguish the basic types and Object
// This is because the num toString process is wrapped as a wrapper object and then unwrapped as the base type
Copy the code

Casts between types

All implicit conversions are based on casts, so it’s important to understand how casts work in JS.

Abstract operation ToString

The ECMAScript fifth edition specification defines the abstract operation ToString. The specification defines the process for casting other types ToString. The javascript method for casting ToString is generally string (…).

Let’s look at the following example:

String(4)                    / / "4"
String(false)                // "false"
String(true)                 // "true"
String(null)                 // "null"
String(undefined)            // "undefined"
String(Symbol('s'))          // "Symbol(s)"
// Base type force-string is specified in the specification and is intuitive

The Object type, however, is slightly different
String({ a: 2 })             // "[object Object]"
String([1.2])               / / 1, 2, ""
String(/reg/g)               // "/reg/g"
// You can see that toString is inconsistent between subtypes of Object
// In fact, when toString is converted to Object,
// The toString method on the prototype chain is called and returned as a result
var arr = [1.2];

arr.toString()             / / 1, 2, ""
String(arr)                / / 1, 2, ""
/ / rewrite the toString
arr.toString = function() { return this.join('/')};String(arr)                / / "1/2"
// Cast Object to string
// It actually calls the toString method on the prototype of the type,
// All subtypes of Object override the toString method
// The toString operation behaves differently

Copy the code

Abstract operation ToNumber

The JS specification also defines other abstractions for casting to number. Take a look at the following example:

Number("4")                  / / 4
Number("4a")                 // NaN
Number("")                   / / 0
Number(false)                / / 0
Number(true)                 / / 1
Number(null)                 / / 0
Number(undefined)            // NaN
Number(Symbol('s'))          // TypeError...
Copy the code

For basic types of casting die were written in the specification, it is important to note Symbol types in the process of compulsory transfer number will quote TypeError, be a pit. ToPrimitive We focus on the conversion process of Object type to number. Before converting an Object to number, it is converted to the base type and then to number. This process is called ToPrimitive.

The ToPrimitive procedure first checks to see if the object has a valueOf method. If it does and valueOf returns a valueOf the primitive type, the value is used for the cast; if not, the value returned by toString is used for the cast.

var arr = [1.2]
Number(arr)    // NaN
// because arr.tostring () equals "1,2", cast NaN

arr.toString = function() { return '43' }
Number(arr)    / / 43

arr.valueOf = function() { return The '42' }
Number(arr)    / / 42

var obj1 = {}
Number(obj1)   // NaN

var obj2 = {
    valueOf: function () {
        return '99'}}Number(obj2)   / / 99
Copy the code

How do implicit conversions work in JavaScript

We’ve talked a lot about casting rules in JS. What does that have to do with implicit typing?

JS in the process of implicit conversion, the actual type complies with the rules of strong conversion, so we explore the nature of implicit conversion is to explore how [] + {} through a series of type conversion into “[object object]”.

The most confusing in implicit conversion should be + operator and the = = operator caused by implicit conversion, because for other types of operator, type of arithmetic -, *, & present and operators, ^, | in design goal is to manipulate Numbers.

Let’s observe the following code:

10 / '2'       // 5 performs the ToNumber operation on string 2
'10' / '5'     // 2 does the ToNumber on both sides of the operator

var obj = {
    valueOf: function() { 
        return '10'}}100 / obj     
// 10 Execute the ToNumber operation on obj

// This is also true for bit operations
0b011 | '0b111'      / / 7
Copy the code

After the easy stuff, let’s move on to the really gross stuff.

Implicit conversion rules on both sides of operator +

In JavaScript, the + sign has the function of concatenating strings in addition to the traditional four operations.

1 + 2  / / 3
'hello' + ' ' + 'world'   // hello world
Copy the code

Ambiguity can be confusing, so when is string concatenation appropriate and when is addition?

Observe the following code:

1 + '1'    / / "11"
1 + true   / / 2
1 + {}     // "1[object Object]"
'1' + {}   // "1[object Object]"
1 + []     / / "1"

var obj = {
    valueOf: function() { return 1}}1 + obj   / / 2

var obj2 = {
    toString: function() { return 3}}1 + obj2  / / 4

var obj3 = {
    toString: function() { return '4'}}1 + obj3 14 "/ /"
Copy the code

It’s a little confusing to look at the example above, but the bottom line is, if one of the operands is a string; Or if one of the operands is an object and can be converted to a string by the ToPrimitive operation, a string concatenation operation is performed. Otherwise, the addition operation is performed.

X + y =>if (type x === string || type y === string ) return join(x, y)
=> if (type x === object && type ToPrimitive(x) === string) return join(x, y)
=> if (type y === object && type ToPrimitive(y) === string) return join(x, y)
=> else return add(x, y)

Copy the code

In the case of addition, if one side of the operand is not number, the ToNumber operation is performed to convert the operand to a numeric type.

Let’s look at two examples:

1 / / examples
[1.2] + {}    / / "1, 2 [object object]." "
/** * [1, 2] and {} are not strings, but [1, 2] and {} can be converted to strings by ToPrimitive *. According to ToPrimitive's rules * [1, 2].valueof () is not the base type, So we use [1, 2].tostring () becomes "1,2" + {} * obviously {} can also be converted to "[object object]" * by the ToPrimitive operation so the final result is "1,2[object object]" **/
  
  
 2 / / examples
 var obj = {
     valueOf: function() { return 12}}true + obj   / / 13
 /** * both true and obj are not strings, And obj cannot be converted to a string by ToPrimitive * so add * ToNumber on true yields 1 * ToPrimitive on obj yields 12 * and 1 + 12 outputs 12 **/
Copy the code

Through the above example believe that everyone has the implicit conversion on both sides of the number of + have a certain understanding, but some of my classmates will say that why the {} + [] = = = 0, this obviously does not accord with the above process, it is indeed a pit, the pit is that the compiler will not think I will not expected to be {} parsed into object, but is parsed into blocks of code.

{} + []
/** * for the compiler, the code block does not return any value * then +[] becomes a forced conversion ToNumber * [] becomes '' via oPrimitive, and finally '' is converted to 0 **/ via ToNumber{}; + [];Copy the code

With that said, you won’t be confused by the first few examples in this article, except on the + side, the most confusing thing is ==, so much so that most front-end teams will disable == via ESLint.

Implicit conversion rules on both sides of the == operator

The == operator is called abstract equality, and it is abstract enough that we would generally advise against using abstract equality in business code.

But sometimes it’s convenient to use abstract equality, like in antD where the value is a string, even though the value is a number.

Before we actually get into the transformation rules for abstract equality, let’s take a look at the special case:

NaN= =NaN        // false, false, false, false
null= =undefined // true, belongs to the ECMA specification
Copy the code

Let’s take a look at how == behaves in other cases:

[1] = =1      // true
false= ='0'  // true
false= =' '   // true
' '= ='0'     // false
true= =1     // true
false= =0    // true
true= = []// false[] = = {}// false

var obj = {
    valueOf: function() { return 1 }
}

obj == 1     // true
/ / to despair[] = =! []// true
Copy the code

The ecMA specification describes the process of comparing abstract equality:

  1. For abstract comparisons of numbers and strings, the strings are ToNumber and then compared
  2. For booleans compared to other types, the Boolean type is ToNumber and then compared
  3. For object and base type comparison, ToPrimitive operation is performed on the object before comparison
  4. Comparison between objects, true if referencing the same object, false otherwise

With that said, let’s analyze some examples based on the rules:

true= ='1'       // true
/** * Booleans and other types apply to rule 2, where true is converted to 1 by ToNumber where 1 == '1' and rule 1 where '1' is converted to 1 * 1 == 1 by ToNumber so the output is true **/

var obj = {
    valueOf: function() { return '1'}}true == obj      // true
/** * First apply to rule 2, convert true to 1, where 1 == obj * applies to rule 3, where obj is '1', where 1 == '1' * applies to rule 1, where '1' is converted to 1, where 1 == 1, so print true **/
  
// We analyze the problems of the next century [] ==! [][] = =! []// true

/** * this is false, but let's look at it carefully. [] casts [] first, so it should actually be [] == false * so we're back to our previous rule, which applies to rule 2 so [] == 0 * and then to rule 3 so "== 0 * and finally ToNumber(") == 0 **/
Copy the code

Here, basically JS more common implicit type overlay and pit are covered, in fact, you can see that the implicit type is not traceless, except for a few special cases, basically are based on some rules for conversion, we just need to remember the conversion rules, can be retractable freely.

Write in the last

Implicit type conversion is a novice learning to front-end often encounter, we often recommend using = = =, give up the understanding of the = =, but even if we don’t use, at the time of some of the code in the learning community, would inevitably encounter someone used, so even if you don’t use the = =, see code may inevitably depends on others, So it’s important to know how it works.

And finally, a few little exercises at the end:

var obj = {
    valueOf: function() { return 42 },
    toString: function() { return The '42'}},var arr = [1.2]

1 + obj
arr + obj
0= = []"" == []
obj == The '42'
Copy the code

Thank you for your reading. If there are any mistakes, please point them out in the comments section. Thank you very much.

Welcome to my other posts:

  • This in the arrow function
  • See the implementation of bind from this mechanism
  • Handwritten promises for front-end interviews