📜 ⬆️ ⬇️

[1] + [2] - [3] === 9 !? Examination of internal type conversion mechanisms in JavaScript

JavaScript allows you to perform type conversion. If this is done intentionally, then we have an explicit type casting (type casting or explicit coercion). In the case where this is done automatically, when you try to perform any operations on values ​​of different types, this is called implicit type conversion (coercion or implicit coercion).
The author of the material, the translation of which we are publishing today, suggests looking at how explicit and implicit type conversions look at a low level. This will allow everyone to better understand the processes hidden in the depths of JavaScript and help give a reasoned answer to the question of why [1] + [2] - [3] === 9.


Explicit type conversion


▍Object wrappers of primitive types


Practically all primitive types in JavaScript (the exceptions are null and undefined ) have object wrappers including their values. Read more about it here . The developer has access to the constructors of such objects. This fact can be used to convert values ​​of one type into values ​​of another type.

 String(123); // '123' Boolean(123); // true Number('123'); // 123 Number(true); // 1 

In the example shown here, wrappers for variables of primitive types do not exist for long: after the job is done, the system gets rid of them.
')
Attention should be paid to this, since the above statement does not apply to cases where the keyword new used in such a situation.

 const bool = new Boolean(false); bool.propertyName = 'propertyValue'; bool.valueOf(); // false if (bool) { console.log(bool.propertyName); // 'propertyValue' } 

Since in this case bool is a new object (and not a primitive value), it is converted to true in the if expression.

Moreover, we can talk about the equivalence of the following two structures. This one:

 if (1) { console.log(true); } 

And this:

 if ( Boolean(1) ) { console.log(true); } 

You can see for yourself by conducting the following experiment, which uses the Bash shell. Place the first code fragment in the file if1.js , the second - in the file if2.js Now do the following:

1. Compile JavaScript code by converting it to assembler code using Node.js.

 $ node --print-code ./if1.js >> ./if1.asm $ node --print-code ./if2.js >> ./if2.asm 

2. Prepare a script to compare the fourth column (here are commands in assembler) of the resulting files. There is no intentional comparison of memory addresses, as they may differ.

 #!/bin/bash file1=$(awk '{ print $4 }' ./if1.asm) file2=$(awk '{ print $4 }' ./if2.asm) [ "$file1" == "$file2" ] && echo "The files match" 

3. Run this script. It will display the following line, which confirms the identity of the files.

 "The files match" 

▍ParseFloat Function


The parseFloat function works in almost the same way as the Number constructor, but it is more freely related to the arguments passed to it. If it encounters a character that cannot be part of a number, then it returns a value, which is a number assembled from the digits before that character and ignores the remainder of the string passed to it.

 Number('123a45'); // NaN parseFloat('123a45'); // 123 

▍ParseInt Function


The parseInt function, after parsing the argument passed to it, rounds the resulting numbers. It can work with values ​​presented in different number systems.

 parseInt('1111', 2); // 15 parseInt('0xF'); // 15 parseFloat('0xF'); // 0 

The parseInt function can either “guess” which number system is used to record the argument passed to it, or it can use the “hint” as the second argument. You can read about the rules applied when using this function in MDN .

This function works incorrectly with very large numbers, so it should not be considered as an alternative to the Math.floor function (by the way, it also performs type casting).

 parseInt('1.261e7'); // 1 Number('1.261e7'); // 12610000 Math.floor('1.261e7') // 12610000 Math.floor(true) // 1 

▍ToString Function


Using the toString function, you can convert other types of values ​​into strings. It should be noted that the implementation of this function in prototypes of objects of different types is different. If you feel that you need to better understand the concept of prototypes in JavaScript, take a look at this material.

String.prototype.toString function


This function returns a value represented as a string.

 const dogName = 'Fluffy'; dogName.toString() // 'Fluffy' String.prototype.toString.call('Fluffy') // 'Fluffy' String.prototype.toString.call({}) // Uncaught TypeError: String.prototype.toString requires that 'this' be a String 

Number.prototype.toString function


This function returns a number converted to a string (as the first argument, you can pass to it the base of the number system in which the result returned by it should be presented).

 (15).toString(); // "15" (15).toString(2); // "1111" (-15).toString(2); // "-1111" 

Symbol.prototype.toString function


This function returns a string representation of a Symbol object. It looks like this: `Symbol(${description})` . Here, in order to demonstrate the operation of this function, the concept of template strings is used .

Boolean.prototype.toString function


This function returns true or false .

Object.prototype.toString function


Objects have an internal value of [[Class]] . It is a tag representing the type of object. The Object.prototype.toString function returns a string of the following form: `[object ${tag}]` . Here, as a tag, either standard values ​​are used (for example, “Array”, “String”, “Object”, “Date”), or values ​​specified by the developer.

 const dogName = 'Fluffy'; dogName.toString(); // 'Fluffy' (  String.prototype.toString) Object.prototype.toString.call(dogName); // '[object String]' 

With the advent of ES6, tags are defined using objects of type Symbol . Let's give a couple of examples. Here is the first.

 const dog = { name: 'Fluffy' } console.log( dog.toString() ) // '[object Object]' dog[Symbol.toStringTag] = 'Dog'; console.log( dog.toString() ) // '[object Dog]' 

Here is the second.

 const Dog = function(name) { this.name = name; } Dog.prototype[Symbol.toStringTag] = 'Dog'; const dog = new Dog('Fluffy'); dog.toString(); // '[object Dog]' 

You can also use ES6 classes with getters.

 class Dog { constructor(name) {   this.name = name; } get [Symbol.toStringTag]() {   return 'Dog'; } } const dog = new Dog('Fluffy'); dog.toString(); // '[object Dog]' 

Array.prototype.toString Function


This function, when called from an Array object, makes a call toString for each element of the array, collects the results into a string whose elements are separated by commas, and returns this string.

 const arr = [ {}, 2, 3 ] arr.toString() // "[object Object],2,3" 

Implicit type conversion


If you know how explicit type conversion works in JavaScript, it will be much easier for you to understand the features of the work of implicit type conversion.

▍Mathematical operators


Plus sign


Expressions with two operands, between which there is a + sign, and one of which is a string, produce a string.

 '2' + 2 // 22 15 + '' // '15' 

If you use the + sign in an expression with one string operand, you can convert it to a number:

 +'12' // 12 

Other Mathematical Operators


When using other mathematical operators, such as - or / , the operands are always converted to numbers.

 new Date('04-02-2018') - '1' // 1522619999999 '12' / '6' // 2 -'1' // -1 

When converting dates to numbers get Unix-time corresponding to the dates.

▍ exclamation mark


Using an exclamation mark in expressions leads to the conclusion true if the initial value is perceived as false, and false for values ​​that are perceived by the system as true. As a result, an exclamation point applied twice can be used to convert different values ​​to their corresponding logical values.

 !1 // false !!({}) // true 

ToToInt32 function and bitwise OR operator


It is worth ToInt32 function, although it is an abstract operation (an internal mechanism that cannot be called in normal code). ToInt32 converts values ​​to ToInt32 32-bit integers .

 0 | true          // 1 0 | '123'         // 123 0 | '2147483647'  // 2147483647 0 | '2147483648'  // -2147483648 ( ) 0 | '-2147483648' // -2147483648 0 | '-2147483649' // 2147483647 ( ) 0 | Infinity      // 0 

Applying a bitwise OR operator in the event that one of the operands is zero and the second is a string will cause the value of the other operand to not change, but will be converted to a number.

▍Other cases of implicit type conversion


In the process of work, programmers may encounter other situations in which implicit type conversion is performed. Consider the following example.

 const foo = {}; const bar = {}; const x = {}; x[foo] = 'foo'; x[bar] = 'bar'; console.log(x[foo]); // "bar" 

This is due to the fact that both foo and bar , when casting them to a string, turn into "[object Object]" . This is what actually happens in this code snippet.

 x[bar.toString()] = 'bar'; x["[object Object]"]; // "bar" 

Implicit type conversion also happens with patterned strings . In the following example we will try to override the function toString .

 const Dog = function(name) { this.name = name; } Dog.prototype.toString = function() { return this.name; } const dog = new Dog('Fluffy'); console.log(`${dog} is a good dog!`); // "Fluffy is a good dog!" 

It is worth noting that the reason why it is not recommended to use the non-strict equality operator ( == ) is the fact that this operator, if the types of the operands do not match, produces an implicit type conversion. Consider the following example.

 const foo = new String('foo'); const foo2 = new String('foo'); foo === foo2 // false foo >= foo2 // true 

Since the new keyword is used here, foo and foo2 are wrappers around primitive values ​​(which is the string 'foo' ). Since the corresponding variables refer to different objects, the result of comparing the form foo === foo2 is false . The >= operator performs implicit type conversion by calling the valueOf function for both operands. Because of this, a comparison of primitive values ​​is made here, and as a result of calculating the value of the expression foo >= foo2 turns out true .

[1] + [2] - [3] === 9


We believe now it is clear to you why the expression [1] + [2] – [3] === 9 true. However, nevertheless, we offer to disassemble it.

1. In the expression [1] + [2] , the operands are transformed into strings using Array.prototype.toString , and then the result is concatenated. As a result, here we have the string "12" .


2. When calculating the expression 12 - [3] , subtraction "3" of 12 will be performed, which gives 9 .


Results


You can find many recommendations, the authors of which advise simply to avoid implicit type conversions in JavaScript. However, the author of this material believes that it is important to understand the features of the work of this mechanism. Probably, you should not try to use it intentionally, but knowing how it works will undoubtedly prove useful in debugging code and help you avoid mistakes.

Dear readers! How do you feel about implicit type casting in javascript?

Source: https://habr.com/ru/post/353562/


All Articles