preface

Js data type conversion, has been this language is more difficult to master a piece of knowledge, because the conversion rules are many and miscellaneous, easy to remember mixed and forgotten.

For this kind of knowledge, the most recommended learning material is to read the ECMAScript standard document directly, because it records the most authoritative and accurate JS syntax rules. This article can be regarded as the interpretation of this specification, and it is recommended to study together with the original document.

MDN website or some information such as js grammar books in order to help readers understand the knowledge faster, will consciously or unconsciously to simplify the structure of knowledge, it will often move or move some wrong knowledge, often with the authors subjective understanding on (bootleg), such as in a lot of books on js implicit casts is said to be dangerous, obscure and bad design, This has led me to see a lot of corporate-level projects using esLint to disallow ==. Admittedly, this makes sense in terms of code to ensure that the team the style is unified, because stand team side, you can’t assume that everyone in = = transformation rules are very clear, so the one-size-fits-all approach, to avoid the abuse of this kind of grammar, but in the actual project development, often backfire, such as your boss let you maintain a project is of great historical baggage (shit), This syntax is used a lot, so instead of complaining about it (running away), what you should do is to grasp the net.js language as a whole, such as the type conversion rules described in this article, so that you know what to expect.

The body of the

First of all, casts in JS always end up returning primitives, so you never hear of casting a string into an object. Js casts can be divided into explicit casts (which occur at run time) and implicit casts (which occur at compile time).

let a = 1 let b = a + "2"; // let c = String(a) + "2"; // An explicit castCopy the code

Transformation rules

Section 9 of the ES5 specification defines some “abstract operation” rules. Here we focus on ToString, ToNumber, and ToBoolean, ToPrimitive.

ToPrimitive

It handles casting of object types to primitive types. ES5 Specification 9.1 Object to primitive type is a common operation in code, which is implemented internally through the DefaultValue operation to execute the conversion rules as follows:

  1. Check whether the object existsvalueOf()Methods. If so, the method is called to see if it returns a primitive type value. If so, the value is cast. If not, go to the next step.
  2. Check whether the object existstoString()Methods. If so, calltoString()If there is a return value, it is used for the cast.

If neither valueOf() nor toString() returns a primitive value, TypeError is raised. Note that the [[Prototype]] Object created with object.create (null) is null and does not inherit the valueOf() and toString() methods.

ToString

It handles casting from non-string to string types. ES5 specification 9.8.

  1. For primitive types values are basically literals that are converted to strings of whatever length they are.

  1. For object type values



Object to string, followToPrimitiveSpecification that attempts to cast an object to a primitive value.

const obj = {}; Obj.valueof ({}) // Returns itself, still an object type, not a basic type, continue toString obj.toString() // returns "[Object object]", is a basic type value, and the type is a string, so the conversion result is itCopy the code

Object. Prototype defines valueOf and toString methods at the top of the prototype chain. These methods are inherited by all instance objects. But the front mentioned ToPrimitive just norms from Object instance itself up for these two methods, rather than the Object like this. Prototype. ToString. Call (xx) direct call Object at the top of the prototype chain corresponding method, The result of ToPrimitive can be modified by redefining these methods on an instance. In fact, most common built-in object types in JS implement their own toString methods, so the following test occurs.

Array. The prototype. ToString. Call ([1, 2, 3]) / / "1, 2, 3" is the Object. The prototype. ToString. Call ([1, 2, 3]) / / "[Object Array]" Array.prototype.toString === Object.prototype.toString // falseCopy the code

The code above confirms that arrays do not follow the methods of the Object.prototype Object. Instead, arrays implement their own toString method, which concatenates the elements of a string with commas and returns them, the re returns a literal representation of the string, and the date returns a timestamp of the string.

ToNumber

It handles casting from non-numeric to numeric types. The ES5 specification 9.3.1 generally calls Number(x), + x, x * 1, etc. The second method is the most popular because it is the simplest.

  1. Base type value



Among themfalse,null' 'convert0.trueconvert1.undefinedconvertNaN. Converts a string to a number, following the syntax of numbers. If the string contains characters that are not numbers, the conversion failsNaN.

  1. Object type value

Most object types converted to numbers are NaN, but arrays and dates are the exception in the above tests, which normally convert to numbers. ToPrimitive: How do ordinary object types translate?

const obj = {a:1}; obj.valueOf(); ToString const strTag = obj.toString(); // Return "[object object]", is a basic type, is a string, not a Number, so perform the following cast Number(strTag) The final result is NaNCopy the code

So why is it different when it comes to arrays?

const arr = []; arr.valueOf(); // Return itself, non-primitive, toString arr.tostring (); // Return '' empty string, primitive type value, Number("); // The empty string ' 'converts to the number 0Copy the code

An empty array is like that, but there are elements in the array

const arr = [1]; arr.valueOf(); ToString const STR = arr.tostring (); // Return itself, non-basic, toString const STR = arr.tostring (); // Return '1', base type value Number(' 1 '); // Turn the number to 1Copy the code

But if you have more than one element, you end up with NaN

Const arr = [1, 2]; arr.valueOf(); ToString const STR = arr.tostring (); // Return itself, non-basic, toString const STR = arr.tostring (); // return '1,2', base type value Number(' 1,2 '); // String to number because there is an illegal comma in the middle, so it ends up as NaNCopy the code

Why is an empty array converted to the number 0? One question remains as to why dates normally convert to numbers. Because the date object redefines the valueOf() method, the call returns a timestamp of the numeric type, it goes straight to the numeric type after the first step of ToPrimitive (judging the value returned by valueOf).

const now = new Date(); Now. The valueOf () / / 1631102131908 now time time stamp Date. The prototype. The valueOf. Call (now) / / 1631102131908 now the timestamp of the time Object. The prototype. The valueOf. Call (now) / / Wed Sep 08 2021 19:55:31 GMT + 0800 (China standard time) Date. The prototype. The valueOf = = = Object.prototype.valueOf // falseCopy the code

ToBoolean

It handles casting from non-Boolean to Boolean types. Undefined, ”, null, 0, and NaN are all false values in JS. Boolean() is changed to false. All other values are true and are changed to true. It is important to note that (new Boolean(false), a wrapper object type, returns true when executing Boolean(new Boolean(false)).

Implicit conversions in operators and conditional judgments

A plus

1 + 2 // 2
1 + '2' // '12' 
false + 1 // 1
Copy the code

Simply put, if one of the operands of + is a string, string concatenation is performed; Otherwise, add numbers. What about types other than numbers and strings? Like array types

[1] + [2,3] // '12,3' 
Copy the code

As usual, use the ToPrimitive rule to convert to the base type of the numeric or string class, ‘1’ + ‘2,3’, with strings on both sides, resulting in ‘12,3’. A pit is often mentioned for + sign concatenation of object types

[] + {}; // "[object Object]" {} + []; / / 0Copy the code

The + operator appears to yield different results depending on whether the first operand is ([] or {}). In fact, in the first line of code, {} appears in the + operator expression, so it is treated as a value (empty object). As mentioned earlier, [] will be cast to “” and {} will be cast to “[object object]”. But in the second line, {} is treated as a separate, empty block of code (doing nothing). You don’t need a semicolon at the end of a block of code, so there’s no syntactic problem. The only code that will be executed is + [], which converts the [] cast to 0… Take a tumble

Get back!

Similar to +, the unary operator! Explicitly casts values of other types to Booleans while inverting true values to false values (or inverting false values to true values). So the most common method of explicitly casting to a Boolean is!! Because of the second! It reverses the result again. In the if (..) . In such a Boolean context, if Boolean(..) is not used And!!!!! , the ToBoolean conversion is automatically implicit. Boolean(..) is recommended. And!!!!! To perform explicit conversions to make the code more legible.

&& and | |

Js | in the | and && are different from other languages, they eventually return is not a Boolean value, but the value of two operands one. However, an implicit cast to ToBoolean still occurs during the judgment process.

let a = 42;
let b = null;
let c = "foo";
if (a && (b || c)) {
 console.log( "yep" );
}
Copy the code

Here a && (b | | c) is, in fact, the result of the “foo” rather than a true, and then by the if foo casts for Boolean value, so the result is true.

Loose equal == vs strict ===

== allows casting in equality comparisons, whereas === does not. ES5 specification 11.9.3

== An implicit cast occurs when comparing values of two different types, casting one or both to the same type before comparing. Comparing x == y, where x and y are values, yields true or false. The comparison is as follows:

  1. If two values ofThe same type, just compare them to see if they are equal.

    For example,42Is equal to the42."abc"Is equal to the "abc". There are a few unconventional situations to be aware of.NaNIs not equal toNaN.+ 0Is equal to the0. Finally, loose equality of objects (including functions and arrays) is defined= =If two objects refer to the same reference address, they are regarded as equal. No cast is performed.
  2. Equality comparison between strings and numbers
  • ifType(x)The number is,Type(y)If it is a string, returnx == ToNumber(y)Results.
  • ifType(x)Is a string,Type(y)If yes, the value is returnedToNumber(x) == yResults.
    let a = 42;
    let b = "42";
    a === b; // false
    a == b; // true
    Copy the code

    Conclusion:The comparison between strings and numbers converts strings to numbers and then compares them.

  1. Other types ofandBoolean typeAn equal comparison between

    = =One of the things that can go wrong istruefalseComparison of equality with other types.
    let x = true;
    let y = "42";
    x == y; // false
    Copy the code

    "42"Is a truth value. Why= =The result is nottrue? specification11.9.3.6-7It goes like this:

  • If Type(x) is Boolean, return ToNumber(x) == y;

  • If Type(y) is Boolean, return x == ToNumber(y).

    To analyze the examples carefully, first: Type(x) is a Boolean, so ToNumber(x) casts true to 1, which becomes 1 == “42”, which is still of different Type. “42” is cast to 42 according to the rule, which becomes 1 == 42, which is false. Let’s do another example

    If ([]) {alert('haha')} 'alert' if ([]) {alert('haha')} 'alert' if ([] == true) {alert('haha')} 'alert' if ([] == true) {alert('haha')} 'alert' ToNumber() is 0, not equal to 1 converted by true on the right.Copy the code

    This is where the weirdest thing about == is that the booleans on both sides of == are cast to numbers.

  1. nullundefinedAn equal comparison between
  • If x isnull, y isundefined, the result istrue.
  • If x isundefined, y isnull, the result istrue. That is to say in the= =nullundefinedIt is one thingCan perform implicit casts to each other.

    One other thing to note,null,undefinedin= =It is not equal to any value ~ except itself and each other
    Null == "// false null == null // of course true null == undefined // trueCopy the code
  1. An equality comparison between an object and a value of a primitive type
  • If Type(x) is a string or number and Type(y) is an object, return x == ToPrimitive(y);

  • Return ToPromitive(x) == y if Type(x) is an object and Type(y) is a string or number. The reason this rule does not include booleans in its base type values is because, as I’ve just explained, it is an exception: any value of type (including object types) that is compared to a Boolean is actually compared to a number converted from a Boolean.

    Now that the rules of == are basically over, you can test the following use cases to see how well you have mastered them

    "0" == null; // false "0" == undefined; // false "0" == false; // true -- no! "0" == NaN; // false "0" == 0; // true "0" == ""; // false false == null; // false false == undefined; // false false == NaN; // false false == 0; // true -- no! false == ""; // true -- no! false == []; // true -- no! false == {}; // false "" == null; // false "" == undefined; // false "" == NaN; // false "" == 0; // true -- no! "" = = []; // true -- no! "" = = {}; // false 0 == null; // false 0 == undefined; // false 0 == NaN; // false 0 == []; // true -- no! 0 = = {}; // false ```Copy the code

What do you say? Did you quit school? There are even more dizzy ones

[] = =! [] // trueCopy the code

This is also a common test of the interview question, before judging, first execute the right! ToBoolean = true; ToBoolean = true; ToBoolean = true; [] = false, according to the special rule of false mentioned above, before judgment, first to the number 0, then, the left [] ToPrimitive operation, return empty string, string and number comparison, according to the rule, empty string into number 0, now equal signs around the number 0, so, the outcome is equal. If you really can’t remember so many == rules, there is a simplification technique.

  • nullwithundefinedLike lovers, faithful and ambiguous, equal only to themselves and each other.
  • Compare two values of different types. If there is an object, convert it to the base value first, and then see if the two types are consistent at the moment. If not, convert it to a number and then compare.

If you have a misjudged use case, go back to the above rules and analyze the changes one by one, and you will see the light at the end of your eyes. Finally, I give you an interview question and solution encountered in the previous bytes.

Implement a function, operation result can meet the expected results are as follows: add (1) (2) / / 3 add (1, 2, 3) (10) / / 16 add (1) (2) (3) (4) (5) / / 15Copy the code

At first, I thought it was easy. It wasn’t a parameter corrification, but when I looked into it, I found something wrong. The parameters of the function were not fixed, and they were always executed after the last call, instead of when the usual parameters were passed. One of the solutions to this question will involve the knowledge of type conversion, the idea can refer to an interview question written by the god of javascript type conversion thinking, can incidentally consolidate the knowledge learned above.

reference

  • The Js you Don’t Know, vol
  • ECMA262 document