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 .0 Or,NaN To return tofalse ; Otherwise returnstrue 。 |
String | Returns if the argument is an empty string (length 0)false ; Otherwise returnstrue 。 |
Symbol | returntrue 。 |
BigInt | If the parameter is0n Returns 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 firstToPrimitive Convert objects, and then pass throughToNumber The 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…
- If m is NaN, return the String
"NaN"
.- If m is +0 or -0, return the String
"0"
.- If m is less than zero, return the string-concatenation of
"-"
and ! NumberToString(-m).- If m is +∞, return the String
"Infinity"
.- 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.
- 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)
- 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
- 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
- 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)
- 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:
- 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.
- The toString method of the function returns the source code string.
- The toString method of a date returns a readable date and time string.
- 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:
- If obj is a basic type, return it directly
- Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
- Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
- Otherwise, JavaScript throws a type error exception.
If it is ToPrimitive(obj, String), the processing is as follows:
- If obj is a basic type, return it directly
- Otherwise, the toString method is called, and if a raw value is returned, JavaScript returns it.
- Otherwise, the valueOf method is called, and if a raw value is returned, JavaScript returns it.
- 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);
- if
obj
Is a basic type and returns directly - Otherwise, call
valueOf
Method, if a raw value is returnedJavaScript
Return it. - Otherwise, call
toString
Method, if a raw value is returnedJavaScript
Return it. - Otherwise,
JavaScript
Throws 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:
- lprim = ToPrimitive(value1)
- rprim = ToPrimitive(value2)
- If lprim is a string or rprim is a string, return the concatenation of ToString(lprim) and ToString(rprim)
- 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:
- Lprim = ToPrimitive(null) Lprim = null because null is a primitive type and returns directly
- Rprim = ToPrimitive(1) Rprim = null since 1 is a primitive type and returns directly
- Neither lprim nor rprim is a string
- 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:
- Lprim = ToPrimitive([]), [] = ToPrimitive([], Number), [] = ToPrimitive([], Number)
- Rprim are similar.
- 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:
- Lprim = ToPrimitive([]), lprim = “”
- Rprim = ToPrimitive({}, Number), ToPrimitive({}, Number), ToPrimitive({}, Number)
- 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:
-
If x and y are of the same type:
-
X is Undefined and returns true
-
X is Null and returns true
-
X is a number:
- X is NaN and returns false
- Y is NaN and returns false
- X is equal to y, returns true
- X is +0, y is -0, return true
- X is -0, y is +0, return true
- Returns false
-
X is a string, return true for full equality, false otherwise
-
X is a Boolean, x and y are both true or false, return true, otherwise return false
-
X and y refer to the same object, return true, otherwise return false
-
-
Return true if x is null and y is undefined
-
Return true if x is undefined and y is null
-
X is a number, y is a string, x == ToNumber(y)
-
X is a string, y is a number, and ToNumber(x) == y
-
X is a Boolean and ToNumber(x) == y
-
Y is a Boolean, x is equal to ToNumber of y.
-
X == ToPrimitive(y)
-
ToPrimitive(x) == y
-
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:
- Return true if x is null and y is undefined
- 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:
- X == ToPrimitive(y)
- 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