Type conversions in JavaScript are complex, error-prone, controversial, and widely used

Please enjoy the pictures below

Does the image above make you feel relaxed and happy? I don’t have one.

Let’s take a look at why this happens with JavaScript. What laws do they observe among themselves?

preface

Converting a value from one type to another is often called type conversion. If we are converted without manually specifying the conversion process, it is called an implicit conversion

Before ES6, JavaScript has six data types: Undefined, Null, Boolean, Number, String, and Object. After ES6, the Symbol and BigInt types were proposed.

The result of an explicit transformation

An explicit conversion means that we explicitly ask JavaScript to convert a value to a value of a type, and then we’ll see what happens when we convert different values to different types of values

Turns a Boolean

Boolean types have only two values, true and false. So their conversion rules are simple

We use the Boolean function to convert a type to a Boolean type

There are only seven values that can be converted to false in JavaScript: false, undefined, unll, value 0, NaN, empty string ”, and BigInt0n

Everything else is converted to true.

ECMAScript2020 Specifications

Attached is the official conversion rule table

The parameter types return
Undefined returnfalse.
Null returnfalse.
Boolean Returns parameters (not converted).
Number If the parameter is+ 0.0Or,NaNTo return tofalse; Otherwise returnstrue
String Returns if the argument is an empty string (length 0)false; Otherwise returnstrue
Symbol returntrue
BigInt If the parameter is0nReturns false; Otherwise returnstrue
Object returntrue
console.log(Boolean()) // false

console.log(Boolean(false)) // false

console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false

console.log(Boolean(+0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false

console.log(Boolean("")) // false

console.log(Boolean(0n)) // false
Copy the code

Turn Number

The parameter types The results of
Undefined returnNaN.
Null return+ 0.
Boolean If argument is true, return1. If argument is false, return+ 0.
Number Return to argument (no conversion).
String It’s complicated. Let’s see. ECMAScript is rules made through lexical grammar
Symbol Raises a TypeError exception.
Object Through the firstToPrimitiveConvert objects, and then pass throughToNumberThe result of converting it is returned

Let’s write some examples to verify this:

console.log(Number()) / / + 0

console.log(Number(undefined)) // NaN
console.log(Number(null)) / / + 0

console.log(Number(false)) / / + 0
console.log(Number(true)) / / 1

console.log(Number("123")) / / 123
console.log(Number("123")) / / - 123
console.log(Number("1.2")) / / 1.2
console.log(Number("000123")) / / 123
console.log(Number("000123")) / / - 123

console.log(Number("0x11")) / / 17

console.log(Number("")) / / 0
console.log(Number("")) / / 0

console.log(Number("123, 123")) // NaN
console.log(Number("foo")) // NaN
console.log(Number("100a")) // NaN
Copy the code

If you pass a string through the Number conversion function, it tries to convert it to an integer or floating point Number, ignores all leading zeros, and returns NaN if any character is not a Number. Given this strict judgment, We also typically use the more flexible parseInt and parseFloat for conversions.

ParseInt parses only integers. ParseFloat parses both integers and floating-point numbers. If a string is prefixed with “0x” or “0x”, parseInt interprets it as a hexadecimal number. Parse as many numeric characters as possible and ignore the rest. NaN is eventually returned if the first non-space character is an illegal numeric direct quantity:

console.log(parseInt("3 abc")) / / 3
console.log(parseFloat("3.14" ABC "")) / / 3
console.log(parseInt("12.34")) / / - 12
console.log(parseInt("0xFF")) / / 255
console.log(parseFloat("1")) / / 0.1
console.log(parseInt("0.1")) / / 0
Copy the code

Turn the String

The parameter types The results of
Undefined return"undefined".
Null return"null".
Boolean If argument is true, return"true". If argument is false, return"false".
Number returnNumberToString(argument).
String Return to the argument.
Symbol Raises a TypeError exception.
Object Returns ToString(ToPrimitive(argument, hint String)v).

Post the ECMAScript definition of the NumberToString method ecma262.docschina.org/#sec-tostri…

  1. If m is NaN, return the String "NaN".
  2. If m is +0 or -0, return the String "0".
  3. If m is less than zero, return the string-concatenation of "-" and ! NumberToString(-m).
  4. If m is +∞, return the String "Infinity".
  5. Otherwise, let n, k, and s be integers such that k ≥ 1, 10k-1 ≤ s < 10k, the Number value for s × 10n-k is m, and k is as small as possible. Note that k is the number of digits in the decimal representation of s, that s is not divisible by 10, and that the least significant digit of s is not necessarily uniquely determined by these criteria.
  6. If k≤n≤ 21, return the string-concatenationof:
    • the code units of the k digits of the decimal representation of s (in order, with no leading zeroes)
    • n-k occurrences of the code unit 0x0030 (DIGIT ZERO)
  7. If 0 < n ≤ 21, return the string-concatenationof:
    • the code units of the most significant n digits of the decimal representation of s
    • the code unit 0x002E (FULL STOP)
    • the code units of the remaining k-n digits of the decimal representation of s
  8. If -6 < n ≤ 0, return the string-concatenation of:
    • the code unit 0x0030 (DIGIT ZERO)
    • the code unit 0x002E (FULL STOP)
    • -n occurrences of the code unit 0x0030 (DIGIT ZERO)
    • the code units of the k digits of the decimal representation of s
  9. Otherwise, if k = 1, return the string-concatenation of:
    • the code unit of the single digit of s
    • the code unit 0x0065 (LATIN SMALL LETTER E)
    • the code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative
    • the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes)
  10. Return the string-concatenation of:
    • the code units of the most significant digit of the decimal representation of s
    • the code unit 0x002E (FULL STOP)
    • the code units of the remaining k-1 digits of the decimal representation of s
    • the code unit 0x0065 (LATIN SMALL LETTER E)
    • the code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative
    • the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes)

Turn the object

The conversion of primitive values to objects is simple, with primitive values being converted to their respective wrapper objects by calling String(), Number(), or Boolean() constructors.

Null and undefined are exceptions, and when used where an object is expected, both raise a TypeError exception and do not perform normal conversions.

var a = 1;
console.log(typeof a); // number
var b = new Number(a);
console.log(typeof b); // object
Copy the code

Object to string and number

Both v object to string and object to number conversions are done by calling a method on the object to be converted. JavaScript objects have two different methods to perform the conversion, toString and valueOf. Note that this is different from the ToString and ToNumber methods mentioned above, which are the real exposed methods.

All objects have a toString method for any value other than null and undefined. In general, this method returns the same result as the String method. The toString method returns a string that reflects the object, but that’s where the complications begin.

When you call the toString method on an Object, you are actually calling the toString method on Object.prototype.

However, many classes in JavaScript define more versions of toString methods, depending on their characteristics. Such as:

  1. The toString method of an array converts each array element to a string, which is combined into a result string by adding commas between the elements.
  2. The toString method of the function returns the source code string.
  3. The toString method of a date returns a readable date and time string.
  4. RegExp’s toString method returns a string representing the regular expression’s immediate quantity.

Reading text too abstract? Let’s just write the example:

console.log(({}).toString()) // [object Object]

console.log([].toString()) / / ""
console.log([0].toString()) / / 0
console.log([1.2.3].toString()) / / 1, 2, 3
console.log((function(){var a = 1; }).toString())// function (){var a = 1; }
console.log((/\d+/g).toString()) // /\d+/g
console.log((new Date(2010.0.1)).toString()) // Fri Jan 01 2010 00:00:00 GMT+0800 (CST)
Copy the code

Another function that converts an object is valueOf, which represents the original valueOf the object. The default valueOf method returns the object itself, and arrays, functions, and regees simply inherit the default method and return the object itself. The exception is the date, which returns one of its content representations: the number of milliseconds since January 1, 1970.

var date = new Date(2017.4.21);
console.log(date.valueOf()) / / 1495296000000
Copy the code

ToPrimitive

The next step is to take a look at ToPrimitive, which is easy once you know the toString and valueOf methods.

Let’s look at specification 9.1, where the function syntax looks like this:

ToPrimitive(input[, PreferredType])
Copy the code

The first parameter, input, represents the input value to be processed.

The second parameter is the PreferredType, which is optional and indicates the type you want to convert to. You can choose from two values, Number or String.

When the PreferredType is not passed, it is equivalent to a String if the input is of date type, and a Number otherwise.

If the input is Undefined, Null, Boolean, Number, or String, the value is returned.

If ToPrimitive(obj, Number) is used, the procedure is as follows:

  1. If obj is a basic type, return it directly
  2. Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
  3. Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
  4. Otherwise, JavaScript throws a type error exception.

If it is ToPrimitive(obj, String), the processing is as follows:

  1. If obj is a basic type, return it directly
  2. Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
  3. Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
  4. Otherwise, JavaScript throws a type error exception.

Get into the business

Now that we’ve covered a lot of the basics, let’s get down to business

Here’s an example:

console.log(1 + '1')
Copy the code

In JavaScript, this should work perfectly, but have you ever wondered why 1 and ‘1’ are of different data types, and why it should work?

This is because JavaScript automatically converts data types, often referred to as implicit conversions. But we know that the + operator can be used for both number addition and string concatenation, so in this case, the number 1 is converted to the string ‘1’ to concatenate, right? Or do you convert the string ‘1’ into the number 1 and add?

I’ll keep you in suspense, although YOU probably know the answer. Today, we’ll cover common scenarios for implicit type conversions.

The unary operator +

console.log(+'1');
Copy the code

When the + operator is used as the unary operator, looking at ES5 specification 1.4.6, ToNumber is called to process the value, which is equivalent ToNumber (‘1’), and the result returns the Number 1.

What about these results down here?

console.log(+[]);
console.log(+['1']);
console.log(+['1'.'2'.'3']);
console.log(+{});
Copy the code

ToPrimitive(Input, Number); ToPrimitive(input, Number); ToPrimitive(input, Number);

  1. ifobjIs a basic type and returns directly
  2. Otherwise, callvalueOfMethod, if a raw value is returnedJavaScriptReturn it.
  3. Otherwise, calltoStringMethod, if a raw value is returnedJavaScriptReturn it.
  4. Otherwise,JavaScriptThrows a type error exception.

In the case of +[], [] calls the valueOf method, which returns an empty array because it is not a raw value, and toString, which returns “”.

When you get the return value, then you call the ToNumber method, “” the corresponding return value is 0, so it returns 0.

And so on for the rest of the examples. The result is:

console.log(+['1']); / / 1
console.log(+['1'.'2'.'3']); // NaN
console.log(+{}); // NaN
Copy the code

The binary operator +

specification

Now the + operator is a binary operator again, because it is also a plus sign in addition, subtraction, multiplication and division

1 + ‘1’ we know the answer is’ 11 ‘, the null + 1, + [] [], [] + {}, {}, {}?

To understand the results of these operations, it is inevitable to start with specifications.

Specification address: es5.github. IO /#x11.6.1

But instead of referring to the specification in paragraphs, I’m going to talk to you about the simplified content.

What exactly happens when you do the + operation? Let’s make this clear according to specification 11.6.1:

When value1 + value2 is calculated:

  1. lprim = ToPrimitive(value1)
  2. rprim = ToPrimitive(value2)
  3. If lprim is a string or rprim is a string, return the concatenation of ToString(lprim) and ToString(rprim)
  4. Returns the result of ToNumber(lprim) and ToNumber(rprim)

Let’s take a few examples:

1. Null and Numbers

console.log(null + 1);
Copy the code

Follow the steps of the specification for analysis:

  1. Lprim = ToPrimitive(null) Lprim = null because null is a primitive type and returns directly
  2. Rprim = ToPrimitive(1) Rprim = null since 1 is a primitive type and returns directly
  3. Neither lprim nor rprim is a string
  4. Returns the result of ToNumber(null) and ToNumber(1)

The following:

The result of ToNumber(null) is 0, and the result of ToNumber(1) is 1

So, NULL + 1 equals 0 + 1, and the final result is the number 1.

This one is easy, but let’s do something a little more complicated:

2. Arrays and arrays

console.log([] + []);
Copy the code

Still in accordance with the specification:

  1. Lprim = ToPrimitive([]), [] = ToPrimitive([], Number), [] = ToPrimitive([], Number)
  2. Rprim are similar.
  3. Lprim and rprim are both strings that perform concatenation

So, [] + [] is equivalent to “” + “”, and the end result is an empty string “”.

Here’s a more complicated one:

3. Arrays and objects

// The result is the same
console.log([] + {});
console.log({} + []);
Copy the code

According to the specification:

  1. Lprim = ToPrimitive([]), lprim = “”
  2. Rprim = ToPrimitive({}, Number), ToPrimitive({}, Number), ToPrimitive({}, Number)
  3. Lprim and rprim are both strings that perform concatenation

So, [] + {} is equivalent to “” + “[object object]”, resulting in “[object object]”.

In the following example, the result can be derived from the sample class:

console.log(1 + true);
console.log({} + {});
console.log(new Date(2017.04.21) + 1) // This is a string or a number
Copy the code

The result is:

console.log(1 + true); / / 2
console.log({} + {}); // "[object Object][object Object]"
console.log(new Date(2017.04.21) + 1) // "Sun May 21 2017 00:00:00 GMT+0800 (CST)1"
Copy the code

Pay attention to

This is all done in console.log. If you type directly from the command line in Chrome or Firebug, you might be surprised to see some different results. For example:

The result of {} + [] is “[object object]”.

No hurry, let’s try adding a parenthesis:

It’s going to be the right value again. Why is that?

In fact, without parentheses, {} is treated as a separate, empty block of code, so {} +[] becomes +[], which becomes 0

The same problem appears with {} + {}, and firefox and Google have different results:

> {} + {}
// Firefox: NaN
// Google: "[object object][object object]
Copy the code

If {} were treated as a separate block of code, this would be equivalent to +{}, equivalent to Number({}), and of course NaN, but Chrome returns the correct value here.

So why does this return the correct value? I don’t know, welcome to answer ~

= = equal

specification

“==” is used to compare whether two values are equal. A conversion occurs when the values to be compared are of different types.

For details on how to use “==” for comparison, see specification 11.9.5:

When executing x == y:

  1. If x and y are of the same type:

    1. X is Undefined and returns true

    2. X is Null and returns true

    3. X is a number:

      1. X is NaN and returns false
      2. Y is NaN and returns false
      3. X is equal to y, returns true
      4. X is +0, y is -0, return true
      5. X is -0, y is +0, return true
      6. Returns false
    4. X is a string, return true for full equality, false otherwise

    5. X is a Boolean, x and y are both true or false, return true, otherwise return false

    6. X and y refer to the same object, return true, otherwise return false

  2. Return true if x is null and y is undefined

  3. Return true if x is undefined and y is null

  4. X is a number, y is a string, x == ToNumber(y)

  5. X is a string, y is a number, and ToNumber(x) == y

  6. X is a Boolean and ToNumber(x) == y

  7. Y is a Boolean, x is equal to ToNumber of y.

  8. X == ToPrimitive(y)

  9. ToPrimitive(x) == y

  10. Returns false

Think it’s too complicated to read norms? Let’s break it down into a few cases:

1. Null, and undefined

console.log(null= =undefined);
Copy the code

Look at steps 2 and 3 of the specification:

  1. Return true if x is null and y is undefined
  1. Return true if x is undefined and y is null

So the result of this example is naturally true.

2. Strings and numbers

console.log('1'= =1);
Copy the code

The answer must be true, so the question is do strings become numbers and comparisons or do numbers become strings and string comparisons?

Look at steps 4 and 5 of the specification:

X == ToNumber(y)

X is a string and y is a number

It turns out, obviously, that it’s all converted to numbers and then compared

Booleans and other types

console.log(true= ='2')
Copy the code

The most common error is when the answer to the question is false, as in this example, which intuitively should be true. After all, Boolean(‘2’) is true, but in this case it is false.

In the final analysis, it depends on the specification. Step 6 and 7 of the specification:

6. If x is a Boolean value, judge ToNumber(x) == y

X ==ToNumber(y)

When one side has a Boolean value, the value of that side is ToNumber, which means that true is converted to 1,

True == ‘2’ = 1 == 2

So when one side is a Boolean, booleans are converted, so use xx == true and xx == false as little as possible because of this nature.

Such as:

/ / do not recommend
if (a == true) {}

/ / advice
if (a) {}
/ / better
if(!!!!! a) {}Copy the code

4. Objects and non-objects

console.log( 42= = [The '42'])
Copy the code

Look at steps 8 and 9 of the specification:

  1. X == ToPrimitive(y)
  1. ToPrimitive(x) == y

In this example, ToPrimitive handles [’42’], calls valueOf, returns the object itself, and calls toString, returns ’42’, so

42 == [’42’] = 42 == ’42’ = 42, and the result is true.

At this point, we have looked at steps 2, 3, 4, 5, 6, 7, 8, and 9, and return false for everything else.

other

Here are a few more examples:

console.log(false= =undefined) Copy codeCopy the code

False == undefined = 0 == undefined does not match the above situation, perform the last step to return false

console.log(false= = [])Copy the code

False == [] = 0 == [] = 0 == ” = 0 == 0, returns true

console.log([] == ! [])Copy the code

Execute first! [] operation, convert to false, equivalent to [] == false equivalent to [] == 0 equivalent to ‘== 0 equivalent to 0 == 0, return true

Here are some final examples of pitfalls:

console.log(false= ="0")
console.log(false= =0)
console.log(false= ="")

console.log(""= =0)
console.log(""= = [])console.log([] == 0)

console.log(""= = [null])
console.log(0= ="\n")
console.log([] == 0)
Copy the code

All of the above returns true

other

In addition to these two cases, there are many other cases where implicit type conversions occur, such as if,? :, &&, etc., but relatively speaking, relatively simple, I will not explain.

reference

JavaScript deep headache type conversion (part 1)

JavaScript deep headache type conversion (part 2)

ECMAScript ® 2018 Language Specification