Have you ever encountered a particularly weird js invisible conversion interview question in an interview, and your first reaction is how could it be like this? It's hard to be confident, how does js calculate and get the result, do you have a deep understanding of its principle? The following will explain its implementation principle in depth.
In fact, the first draft of this article was written three months ago. When I was reading some source code libraries, I encountered these basic knowledge and wanted to file and sort it out, so I came across this article. Since I have been busy and have no time to sort it out, I recently saw this hot topic and decided to sort out this article.
const a = { i: 1, toString: function () { return a.i++; } } if (a == 1 && a == 2 && a == 3) { console.log('hello world!'); }
There are many good analysis processes on the Internet. After reading the following content, you will have a deeper understanding of its execution process.
1. js data type
There are 7 data types in js, which can be divided into two categories: primitive type and object type:
Basic type (primitive value):
Undefined, Null, String, Number, Boolean, Symbol (es6 New, this article does not discuss this type)
Complex type (object value):
object
2. Three implicit conversion types
One of the difficulties in js is js invisible conversion, because js will make some changes in its type under some operators, so js is flexible, and at the same time it is error-prone and difficult to understand.
The two operators + and == that involve the most implicit conversions.
The + operator can add numbers or strings. So it is very troublesome to convert. == is different from ===, so there is also an implicit conversion. - */ These operators are only for the number type, so the conversion result can only be converted to the number type.
Since there is an implicit conversion, how to convert it? There should be a set of conversion rules to track what is finally converted.
There are three main types of conversions involved in implicit conversions:
1. Convert the value to the original value, ToPrimitive().
2. Convert the value to a number, ToNumber().
3. Convert the value to a string, ToString().
2.1. Convert the value to the original value through ToPrimitive
The abstract operation ToPrimitive inside the js engine has such a signature:
ToPrimitive(input, PreferredType?)
input is the value to be converted, and PreferredType is an optional parameter, which can be Number or String. It is just a conversion flag, and the converted result is not necessarily the type of the value of this parameter, but the converted result must be an original value (or an error is reported).
2.1.1. If the PreferredType is marked as Number, the following operation process will be performed to convert the input value.
1,If the input value is already a primitive value, return it directly 2,Otherwise, if the input value is an object, call the object's valueOf()method, if valueOf()If the return value of the method is a primitive value, the primitive value is returned. 3,Otherwise, call the object's toString()method if toString()If the method returns a primitive value, the primitive value is returned. 4,Otherwise, throw TypeError abnormal.
2.1.2. If the PreferredType is marked as String, the following operation process will be performed to convert the input value.
1,If the input value is already a primitive value, return it directly 2,Otherwise, call the object's toString()method if toString()If the method returns a primitive value, the primitive value is returned. 3,Otherwise, if the input value is an object, call the object's valueOf()method, if valueOf()If the return value of the method is a primitive value, the primitive value is returned. 4,Otherwise, throw TypeError abnormal.
Since PreferredType is an optional parameter, how to convert it if there is no such parameter? The value of PreferredType will be automatically set according to the following rules:
1,The object is Date type, then PreferredType set as String 2,otherwise, PreferredType set as Number
2.1.3, valueOf method and toString method analysis
The above mainly mentioned the valueOf method and the toString method, so do these two methods necessarily exist in the object? The answer is yes. Output Object.prototype on the console, and you will find that there are valueOf and toString methods, and Object.prototype is the top-level prototype of all object prototype chains. All objects will inherit the methods of this prototype, so any object will have valueOf and toString methods.
First look at the valueOf function of the object, what is the conversion result? Common built-in objects for js: Date, Array, Math, Number, Boolean, String, Array, RegExp, Function.
1. The object form of the basic value generated by the three constructors of Number, Boolean, and String will become the corresponding original value after conversion by valueOf. Such as: reference Detailed answers to front-end advanced interview questions
var num = new Number('123'); num.valueOf(); // 123 var str = new String('12df'); str.valueOf(); // '12df' var bool = new Boolean('fd'); bool.valueOf(); // true
2. For a special object like Date, the built-in valueOf function on its prototype Date.prototype converts the date into a value in the form of milliseconds of the date.
var a = new Date(); a.valueOf(); // 1515143895500
3. In addition to this, what is returned is this, that is, the object itself: (please let me know if you have any questions)
var a = new Array(); a.valueOf() === a; // true var b = new Object({}); b.valueOf() === b; // true
Let's take a look at the toString function again. What is the conversion result? Common built-in objects for js: Date, Array, Math, Number, Boolean, String, Array, RegExp, Function.
1. Objects generated by constructors such as Number, Boolean, String, Array, Date, RegExp, and Function will become corresponding strings after toString conversion, because these constructors encapsulate their own toString methods. Such as:
Number.prototype.hasOwnProperty('toString'); // true Boolean.prototype.hasOwnProperty('toString'); // true String.prototype.hasOwnProperty('toString'); // true Array.prototype.hasOwnProperty('toString'); // true Date.prototype.hasOwnProperty('toString'); // true RegExp.prototype.hasOwnProperty('toString'); // true Function.prototype.hasOwnProperty('toString'); // true var num = new Number('123sd'); num.toString(); // 'NaN' var str = new String('12df'); str.toString(); // '12df' var bool = new Boolean('fd'); bool.toString(); // 'true' var arr = new Array(1,2); arr.toString(); // '1,2' var d = new Date(); d.toString(); // "Wed Oct 11 2017 08:00:00 GMT+0800 (China Standard Time)" var func = function () {} func.toString(); // "function () {}"
Except for these objects and their instantiated objects, all other objects return the type of the object (please let me know if you have any questions), and they all inherit the Object.prototype.toString method.
var obj = new Object({}); obj.toString(); // "[object Object]" Math.toString(); // "[object Math]"
From the conversion of the two functions of valueOf and toString above, we can see why for ToPrimitive(input, PreferredType?), when the PreferredType is not set, except for the Date type, the PreferredType is set to String, and the others are set to Number.
Because the valueOf function will convert the object type values of the Number, String, and Boolean basic types to the basic type, the Date type will be converted to milliseconds, and the others will return the object itself, and the toString method will convert all objects to strings. Obviously, for most object conversions, valueOf conversion is more reasonable, because the conversion type is not specified, and the original value should be kept as much as possible, instead of converting it to a string like the toString method.
Therefore, when the PreferredType type is not specified, it is better to convert the valueOf method first, so set the PreferredType to the Number type.
For the Date type, it performs valueOf conversion to the number type of milliseconds. When performing implicit conversion, when it is not specified to convert it to the number type, it is obviously meaningless to convert it to such a large number type value. (Whether it is in the + operator or the == operator) it is not as good as converting to a date in string format, so the default Date type will give priority to toString conversion. So there are the above rules:
When PreferredType is not set, for objects of Date type, PreferredType is set to String by default, and for other types of objects, PreferredType is set to Number by default.
2.2. Convert the value to a number by ToNumber
The following conversions are performed according to the parameter type:
parameter | result |
---|---|
undefined | NaN |
null | +0 |
Boolean value | true converts to 1, false to +0 |
number | No need to convert |
string | There are strings parsed as numbers, for example: '324' is converted to 324, 'qwer' is converted to NaN |
object (obj) | First perform ToPrimitive(obj, Number) conversion to get the original value, then perform ToNumber conversion to a number |
2.3. Convert the value to a string through ToString
The following conversions are performed according to the parameter type:
parameter | result |
---|---|
undefined | 'undefined' |
null | 'null' |
Boolean value | Converts to 'true' or 'false' |
number | Number conversion string, for example: 1.765 to '1.765' |
string | No need to convert |
object (obj) | First perform ToPrimitive(obj, String) conversion to get the original value, then perform ToString conversion to a string |
After talking so much, is it still not very clear, let's take a look at an example:
({} + {}) = ? The values of the two objects are+Operators must be implicitly converted to primitive types before calculations can be performed. 1,conduct ToPrimitive conversion, since no specified PreferredType Types of,{}would make the default value of Number,conduct ToPrimitive(input, Number)operation. 2,so will execute valueOf method,({}).valueOf(),return still{}Objects, not primitive values. 3,continue to execute toString method,({}).toString(),return"[object Object]",is the original value. Therefore, the final result is obtained,"[object Object]" + "[object Object]" = "[object Object][object Object]"
Another example of specifying a type:
2 * {} = ? 1,first*operator can only be used for number type, so the first step is to{}conduct ToNumber Type conversion. 2,because{}is an object type, so the original type conversion is performed first, ToPrimitive(input, Number)operation. 3,so will execute valueOf method,({}).valueOf(),return still{}Objects, not primitive values. 4,continue to execute toString method,({}).toString(),return"[object Object]",is the original value. 5,Convert to original value and then do ToNumber operation,"[object Object]"just convert to NaN. So the final result is 2 * NaN = NaN
3. == operator implicit conversion
The rules of the == operator are not so regular, follow the following process to execute, es5 document
comparison operation x==y, in x with y is the value, return true or false. Such a comparison is performed as follows: 1,like Type(x) and Type(y) same, then 1* like Type(x) for Undefined, return true. 2* like Type(x) for Null, return true. 3* like Type(x) for Number, but (1),like x for NaN, return false. (2),like y for NaN, return false. (3),like x and y are equal values, return true. (4),like x for +0 and y for −0, return true. (5),like x for −0 and y for +0, return true. (6),return false. 4* like Type(x) for String, then when x with y Returns if they are identical sequences of characters (equal length and same characters in the same position) true. Otherwise, return false. 5* like Type(x) for Boolean, when x with y for the same true or both false return when true. Otherwise, return false. 6* when x with y is returned when referring to the same object true. Otherwise, return false. 2,like x for null and y for undefined, return true. 3,like x for undefined and y for null, return true. 4,like Type(x) for Number and Type(y) for String,back to compare x == ToNumber(y) the result of. 5,like Type(x) for String and Type(y) for Number,back to compare ToNumber(x) == y the result of. 6,like Type(x) for Boolean, back to compare ToNumber(x) == y the result of. 7,like Type(y) for Boolean, back to compare x == ToNumber(y) the result of. 8,like Type(x) for String or Number,and Type(y) for Object,back to compare x == ToPrimitive(y) the result of. 9,like Type(x) for Object and Type(y) for String or Number, back to compare ToPrimitive(x) == y the result of. 10,return false.
The above is mainly divided into two categories, when the types of x and y are the same, and when the types are different.
When the types are the same, there is no type conversion, mainly note that NaN is not equal to any value, including itself, that is, NaN !== NaN.
When the types are different,
1. x,y is either null or undefined // return true
2. When x and y are Number and String types, they are converted to Number types for comparison.
3. When there is Boolean type, Boolean is converted to Number type for comparison.
4. An Object type, a String or Number type, after converting the Object type, compare the original values according to the above process.
3.1, == example analysis
So when the types are different, you can compare the above items, for example:
var a = { valueOf: function () { return 1; }, toString: function () { return '123' } } true == a // true; first, x and y different types, x for boolean type, then proceed ToNumber convert to 1,for number Types of. then, x for number,y for object type, yes y do the original conversion, ToPrimitive(a, ?),No conversion type specified, the default number Types of. then, ToPrimitive(a, Number)First call valueOf method, return 1, get the original type 1. last 1 == 1, return true.
Let's take another look at a very complicated comparison, as follows:
[] == !{} // 1,! Operator precedence is higher than==,So go ahead! operation. 2,!{}The result of the operation is false,The result becomes [] == false Compare. 3,According to item 7 above, the right-hand side of the equation y = ToNumber(false) = 0. The result becomes [] == 0. 4,Following clause 9 above, the comparison becomes ToPrimitive([]) == 0. Convert the original value according to the above rules,[]will call first valueOf function, returns this. is not the original value, continue to call toString method, x = [].toString() = ''. So the result is '' == 0 Compare. 5,According to item 5 above, the left side of the equation x = ToNumber('') = 0. So the result becomes: 0 == 0,return true,The comparison is over.
Finally, let's look at the topic mentioned at the beginning of the article:
const a = { i: 1, toString: function () { return a.i++; } } if (a == 1 && a == 2 && a == 3) { console.log('hello world!'); }
1. When a == 1 && a == 2 && a == 3 is executed, it will be parsed step by step from left to right. First, a == 1, and the conversion in step 9 above will be performed. ToPrimitive(a, Number) == 1.
2. ToPrimitive(a, Number), according to the above primitive type conversion rules, the valueOf method will be called first, and the valueOf method of a is inherited from Object.prototype. Return a itself, not the original type, so the toString method will be called.
3. Because toString is rewritten, the rewritten toString method will be called, so 1 will be returned. Note that this is i++, not ++i. It will return i first, and then i+1. So ToPrimitive(a, Number) = 1. That is 1 == 1, at this time i = 1 + 1 = 2.
4. After executing a == 1 and returning true, a == 2 will be executed. Similarly, ToPrimitive(a, Number) will be called, as above, the valueOf method is called first, and the toString method is called. Because of the first step, i = 2 When , ToPrimitive(a, Number) = 2, that is, 2 == 2, at this time i = 2 + 1.
5. As above, it can be deduced that a == 3 also returns true. Therefore, the final result a == 1 && a == 2 && a == 3 returns true
In fact, after understanding the above principles of invisible conversion, have you found that these implicit conversions are not as difficult as imagined.