For a summary and analysis of casts in JavaScript, see chapter 4 of JavaScript you Don’t Know (Middle Volume).

Article mainly divides into five parts, the first part tells the basic types of data transformation of 4 kinds of abstract operations, the second part tells the explicit casts, the third part tells the implicit casts, the fourth part tells equal relationship, which need to master “the abstract equal comparison algorithm”, coupled with the first part of the four kinds of abstract operations, Basically can clear JS type conversion process, the fifth part on the comparison.

1. Abstract value operations

1.1 the ToString

The abstract operation ToString is responsible for handling non-string-to-string casts.

The stringification rules for values of basic types are: null to null,undefined to undefined, and true to “true”. The stringing of numbers follows the general rule that very small and very large numbers use the exponential form:

Var a = 1.07*1000*1000*1000*1000*1000*1000*1000 *1000*1000*1000"1.07 e21"
Copy the code

For ordinary objects, toString() returns the value of an internal attribute [[Class]], such as “[object object]”, unless defined.

The default toString() method for arrays has been redefined toString all elements together with “,” :

Var a = [1,2,3] a.tostring () //"1, 2, 3"
Copy the code

1.2 ToNumber

The abstract operation ToNumber converts a non-numeric value to a numeric value.

Where true is converted to 1, false to 0, undefined to NaN, and null to 0.

ToNumber’s handling of strings basically follows the rules for numeric constants (strings containing non-numeric characters return NaN).

Objects (including arrays) are first cast to the corresponding primitive type value, and if a non-numeric primitive type value is returned, it is then cast to a number following the above rules.

1.3 ToPrimitive

The abstract method ToPrimitive converts the object value to the corresponding primitive type value. The method first checks if the value has a valueOf() method, and if it does and returns a primitive type value, it uses that value for the cast; If not, use the return value of toString() (if present) for the cast; If neither valueOf() nor toString() returns a base type value, TypeError is generated.

Starting with ES5, objects created with object.create (null) have null prototype attributes and no valueOf() and toString() methods, so casting cannot be performed.

1.4 ToBoolean

The abstract operation ToBoolean converts a non-Boolean value to a Boolean value.

1.4.1 false value

A false Boolean cast results in false.

These are false values:

  • undefined
  • null
  • false
  • +0, -0, and NaN
  • “”

Logically, anything outside the list of false values should be true, but the JavaScript specification doesn’t explicitly define this, giving examples such as specifying that all objects are true.

1.4.2 False value objects

Such as:

var a = new Boolean(false)
var b = new Boolean(0)
var c = new Boolean("")

var d = Boolean(a && b && c)    //true
Copy the code

A,b, and c are all objects that encapsulate false values, but d is true, indicating that a, B, and C are true. So a false value object is not an object that encapsulates a false value.

False objects look exactly like normal objects, but when cast to a Boolean value, the result is false. The most common example is Document. all, which is an array-like object that contains all the elements on a page. It used to be a true object, with a Boolean cast of true, but is now a false value object.

1.4.3 true value

A true value is a value outside the false value list. The truth list can be infinitely long and cannot be enumerated, so the false list can only be used as a reference.

2. Display the cast

2.1 Display conversion between strings and numbers

The conversion between a String and a Number is done using the two built-in functions String() and Number(). Note that they are not preceded by the new keyword and do not create encapsulated objects.

String() follows the ToString rule described earlier, converting a value to a String primitive. Number() follows the ToNumber rule described earlier, converting a value to a numeric primitive type.

In addition to String() and Number(), there are other ways to convert the display between strings and numbers:

var a = 42
var b = a.toSting() //"42"

var c = "3.14"Var d = +c //Copy the code

A.tostring () is explicit, but involves an implicit conversion. Because toString() doesn’t work for a primitive type value like 42, the JavaScript engine automatically creates a wrapper object for 42 and then calls toString() on that object.

In the above example +c is the unary form of the + operator (that is, there is only one operand). The + operator explicitly converts C to a number, not numeric addition (nor string concatenation).

Unary operator – the same as + and also reverses the sign bit of the number. Since — is treated as the decrement operator, we cannot use — to undo the inversion, but should add a space in the middle like “3.14”.

Try not to use the unary operator + (and -) with other operators. And d = +c is also easy to confuse with d += c, which is quite different.

2.1.1 Date display is converted to numbers

Another common use of the unary operator + is to cast a Date object to a number, returning a Unix timestamp as a result.

var time = new Date()
+time
Copy the code

2.1.2 The peculiar ~ operator

~ first casts the value to a 32-bit number, then performs the byte operation “not” (reverses each bit).

Bit inversion is an obscure topic, and JavaScript developers generally have little to worry about at the bit level.

There is another way to interpret ~ : return the complement of 2!

So delta x is roughly the same thing as minus x plus 1.

~ 42 / / - (42 + 1) = = > - 43Copy the code

The only x value in -(x+1) that leads to 0(or, strictly speaking, -0) is -1, whereas some JavaScript functions use -1 to indicate failure and 0 or greater to indicate success.

For example, the indexOf() method searches the string for the specified string and returns the location of the substring if it finds it, or -1 otherwise.

var a = "Hello World"
if(a.indexOf("lo")! = 1){// find a match}if(a.indexOf("ol") == -1){// No match was found}Copy the code

Together with indexOf(), ~ casts the result to true/false, and if indexOf() returns -1, ~ converts it to false 0, otherwise casting to true.

var a = "Hello World"
~a.indexOf("lo") // -4 ==> true valueif(~a.indexOf("lo"){// find a match} ~ a.indexof ("ol") // 0 ==> False valueif(! ~a.indexOf("ol"){// No match was found}Copy the code

2.1.3 Character bit interception

~~x can cut the value into a 32 integer. The first ~ in ~~ performs ToInt32 and reverses the bit, and then the second ~ performs another bit reversal, reversing all the bits back to the original value. The result is still ToInt32.

First, ~~ only works with 32-bit numbers, and more importantly, it treats negative numbers differently than math.floor ().

Math.floor(-49.6)   // -50
~~-49.6 //-49
Copy the code

2.2 Explicit Parsing of numeric strings

Parsing a number in a string and converting a string cast to a number both return numbers. But there is a significant difference between parsing and transformation. Such as:

var a = "42"
var b = "42px"

Number(a)   //42
parseInt(a) //42

Number(b)   //NaN
parseInt(b) //42
Copy the code

Parsing is allowed in strings with non-numeric characters in left-to-right order, stopping if non-numeric characters are encountered. Conversion does not allow non-numeric characters, otherwise NaN will fail to be returned.

Parsing floating point numbers in strings can be done using the parseFloat() function. Starting with ES5, parseInt() is converted to decimal by default, unless a second argument is specified as the base.

Remember that parseInt() is for strings, and passing numbers and other types of arguments to parseInt() is useless. Non-strings are cast to strings first, and you should avoid passing non-string arguments to parseInt().

/ / 18 parseInt (1/0, 12)Copy the code

ParseInt (1/0,19) results in 18 instead of an error because parseInt(1/0,19) is actually parseInt(“Infinity”,19). Base 19, whose significant numeric characters range from 0 to 9 and a-i(case sensitive). In base 19, the first character “I” has the value of 18, and the second character “n” is not a valid numeric character. Parsing ends here, as in “P” in “42px”.

2.3 Explicit conversion to A Boolean value

And the + type, unary operator! Explicitly casts a value cast to a Boolean value. But it also inverts the truth value to a false value. So the most common way to explicitly cast a Boolean is to!! .

In Boolean contexts such as if(), it is recommended to use Boolean() and!! To perform explicit conversions to make the code more legible.

3. Implicit cast

3.1 Implicit casting between strings and numbers

The ES5 specification defines that + will be concatenated if an operand is a string or can be converted to a string by the following steps. If one of the operands is an object (including an array), the ToPrimitive abstract operation is called on it first, which in turn calls [[DefaultValue]], with the number as the context.

To put it simply, if one of the operands of + is a string (or can be obtained by the above steps), then string concatenation is performed, otherwise number addition is performed.

Var a = [1,2] var b = [3,4] a + b //"1,23,4"
Copy the code

Because the valueOf() operation on the array cannot get a simple primitive value, toString() is called, so the two arrays become “1,2” and “3,4”. + concatenates them and returns.

There is a slight difference to note between a + “”(implicit) and the previous String(a)(explicit). According to the ToPrimitive abstract operation rule, a + “” calls the valueOf() method on A and then converts the return value to a String through the ToString abstraction, whereas String(a) calls the ToString () method directly.

3.2 Implicit cast from Boolean to number

OnlyOne () returns true if one and onlyOne of the arguments is true.

function onlyOne() {
    var sum = 0
    for (var i=0; i<arguments.length; i++){if(arguments[i]){
            sum += arguments[i]
        }
    }
    return sum == 1
}

var a = true
var b = false
onlyOne(b,a,b,b,b,b)    // true
Copy the code

With either implicit or explicit conversions, we can modify onlyTwo() or onlyFive() to handle more complex cases by changing the final conditional judgment from 2 or 5. This than to join a lot of && and | | expression is much more concise.

3.3 Implicit Cast to Boolean

Implicit casts in number and string operations are more obvious than booleans. Boolean implicit casts occur in the following cases.

  • A conditional expression in an if() statement
  • for(.. ; . ; ..) A conditional expression in a statement
  • While () and do.. while()
  • ? The conditional judgment expression in:
  • Logical operators | | and && the left operand

3.4 | | and &&

Calling them “logical operators” is not a good idea, because it’s not very accurate. It is more appropriate to call them “selector operators” or “operand selector operators”.

ES5 specification that && and | | operators return values is not necessarily a Boolean type, but the value of two operands one.

For | |, if the condition judgment result to true will return the value of the first operand, if to false will return the value of the second operand.

For &&, the value of the second operand is returned if true, and the value of the first operand if false.

** Here the conditional result refers to the left-hand operand.

The following is a very common | | usage, you may have used but not fully understand:

function foo(a,b) {
    a = a||"hello"
    b = b||"world"
    console.log(a + ' ' + b)
}
foo()   // "hello world"
Copy the code

Again, there is a usage that developers don’t see very often, but JavaScript code compression tools do. The && operator selects the second operand as the return value if the first operand is true. This is also called a “daemon operator”, where the preceding expression checks for the following expression.

function foo() {
    console.log(a)
}
var a = 42 
a && foo()
Copy the code

3.5 Mandatory Type Conversion

The conformance type was introduced in ES6, and its cast has a pit. ES6 allows explicit casts from conformance to strings, however implicit casts produce errors such as:

var s1 = Symbol("cool")
String(s1)  // "Symbol(cool)"
var s2 = Symbol("not cool")
s2 + ' ' // TypeError
Copy the code

Conformance cannot be cast to a number (both explicit and implicit produce an error), but can be cast to a Boolean (both explicit and implicit are true).

4. Loose equality and strict equality

The common mistake is “== checks for value equality, === checks for value and type equality”. The correct interpretation is: “== allows casting in equality comparisons, while === does not.” In fact, both == and === check the type of the operand. The difference is that they are handled differently depending on the operand type.

4.1 Abstract Equality

The “Abstract Equality Comparison Algorithm” in section 11.9.3 of the ES5 specification defines the behavior of the == operator. The algorithm is simple and comprehensive, covering all possible type combinations and how they cast.

The comparison operation x==y, where x and y are values, producestrueorfalse. 1. If Type(x) is the same as Type(y), then a. Return if Type(x) is Undefinedtrue. B. If Type(x) is Null, the value is returnedtrue. C. If Type(x) is Number, I. If x is NaN, this field is returnedfalse. Ii. If y is NaN, returnfalse. Iii. If x and y are equal, returntrue. Iv. If x is +0 and y is −0, returntrue. V. If x is −0 and y is +0, return the valuetrue. Vi. Returnfalse. D. If Type(x) is String, this field is returned when x and y are identical character sequences (of equal length and same characters in the same position)true. Otherwise, returnfalse. E. If Type(x) is Boolean, x and y are the sametrueOr asfalseWhen to return totrue. Otherwise, returnfalse. F. Return if x and y refer to the same objecttrue. Otherwise, returnfalse. 2. If x is null and y is undefined, returntrue. 3. If x is undefined and y is null, returntrue. 4. If Type(x) is Number and Type(y) is String, comparison x == ToNumber(y) is returned. 5. If Type(x) is String and Type(y) is Number, the result of comparing ToNumber(x) == y is returned. 6. If Type(x) is Boolean, return ToNumber(x) == y. 7. If Type(y) is Boolean, return the result of comparing x == ToNumber(y). 8. If Type(x) is String or Number and Type(y) is Object, the comparison result of x == ToPrimitive(y) is returned. 9. If Type(x) is Object and Type(y) is String or Number, ToPrimitive(x) == y is returned. 10. Returnfalse.Copy the code

4.1.1 Equality comparison between strings and numbers

var a = 42
var b = The '42'
a == b  //true
Copy the code

A ==b is loosely equal, that is, if two values are of different types, one or both are cast. How to convert? This requires matching the previous “abstract equality comparison algorithm” to find suitable transformation rules.

Return x == ToNumber(y) according to rule 4.

4.1.2 Equality comparison between other types and Boolean types

One of the most common things that can go wrong with == is equality comparisons between true and false and other types.

var a = The '42'
var b = true
a == b  //false
Copy the code

The result is false, which makes it easy to fall into a pit. This result is expected if strictly followed by the abstract equality comparison algorithm.

According to rule 7, if Type(y) is Boolean, return the result of comparing x == ToNumber(y), that is, return ’42’ == 1, false.

Strange, right? So don’t use == true and == false under any circumstances.

4.1.3 Equality comparison between null and undefined

Null and undefined are equal in ==, which means that null and undefined are the same thing in == and can be implicitly cast to each other.

** master the “abstract equality comparison algorithm”, readers can find why []==! [] returns true.

4.2 Relatively rare circumstances

if(a == 2 && a == 3) {/ /...
}
Copy the code

You might think that’s impossible, because A can’t be equal to both 2 and 3. But this is what happens if you make a.valueof () every time you call it produce side effects, such as returning 2 the first time and 3 the second time.

var i = 2
Number.prototype.valueOf = function() {
  return i++
}
var a = new Number(42)
if(a == 2 && a == 3) {console.log('Yeah, it happened! ')}Copy the code

Another pit is often mentioned:

0= ='\n'   //true
Copy the code

Empty strings such as “”, “\n”(or “” and other combinations of Spaces) are cast to 0 by ToNumber.

4.3 Integrity Check

And look at the “short” places:

"0"= =false    // true
false= =0      // true
false= =""     // true
false= = []// true
""= =0          // true
""= = []// true
0= = []// true
Copy the code

Four of these cases involve == false, which we said should be avoided, so that leaves the last three.

These special conditions can cause problems and should be used with care. We need to carefully evaluate the values on both sides of the equation, and the following two principles can help us avoid errors.

  • Never use == if either value is true or false
  • Try not to use == if both values have [], “”, or 0

Implicit casts can be dangerous in some cases, so use === to be safe

5. Comparison of abstract relationships

A less than comparison of x and y values (comparisons of x < y) yields =”” true, false, or undefined (this means that at least one of the operands of x and y is NaN). In addition to x and y, the algorithm takes a Boolean token named LeftFirst as a parameter. This flag is used for parsing order control because operands x and y have potentially visible side effects when executed. The LeftFirst flag is required because ECMAScript dictates that expressions are executed from left to right. The default value of LeftFirst is true, which indicates that the argument x comes before the argument y in the related expression. If LeftFirst is false, the opposite is true and the operands must be executed y before x. Such a less than comparison execution step is as follows:

1. If the LeftFirst flag istrueA. Make px the result of calling ToPrimitive(x, hint Number). B. Make py the result of calling ToPrimitive(y, hint Number). 2. Otherwise, the order of interpretation execution needs to be reversed to ensure the execution order from left to right a. Make py the result of calling ToPrimitive(y, hint Number). B. Make px the result of calling ToPrimitive(x, hint Number). 3. If Type(px) and Type(py) do not return String, then a. Let nx be the result of calling ToNumber(px). Because px and PY are primitive data types (primitive values), the order in which they are executed doesn't matter. B. Make ny the result of calling ToNumber(py). C. If nx is NaN, return undefined D. If ny is NaN, undefined e is returned. If nx and ny have the same numeric values, returnfalseF. If nx is +0 and ny is -0, return flase G. If nx is minus 0 and ny is plus 0, returnfalseH. If nx is +∞, return fasle I. If ny is +∞, returntrueJ. If ny is -∞, return flase k. If nx is -∞, return flase ktrueL. If the mathematical value of nx is less than the mathematical value of ny (note that none of these mathematical values can be infinite and cannot all be 0), return true. Otherwise returnsfalse. 4. Otherwise, px and py are Strings a. Return if py is a prefix to pxfalse. The string p is the prefix of q when the value of the string q can be a concatenation of the string p with another string r. Note: any string is its own prefix, because r may be an empty string. B. Return if px is the prefix of pytrue. C. Make k the smallest non-negative integer so that the characters at position k in the string px are different from those at position k in the string py. D. Let m be the encoding unit of the character at position k in the string px. E. Make n the encoding unit value of the character at position k in string py. F. If n<m, returntrue. Otherwise, returnfalse.Copy the code

The following example is a bit odd:

var a = {b:42}
var b = {b:43}

a < b   // false
a == b  // false
a > b   // false

a <= b  // true
a >= b  // true
Copy the code

If a < b and a == b are false, why are a <= b and a >= b true?

Because a <= b is treated as b < a according to the specification, and then the result is reversed. Since b < a is false, a <= b is true.

This may be quite different from what we expect, which is that <= should be “less than or equal to”. In fact, <= in JavaScript means “not greater than”, meaning that a <= b is treated as! (b (a).

The ** specification sets NaN to be neither greater nor less than any other value.