[1] + [2] - [3] === 9 !? Exploring JavaScript Inner Casts

Original author: Administrator wanago.io
  • Transfer
JavaScript allows for type conversion. If this is done intentionally, then we have before us an explicit type conversion (type casting or explicit coercion). In the case when this is done automatically, when trying to perform any operations on values ​​of various types, this is called implicit type conversion (coercion or implicit coercion).
The author of the material, the translation of which we publish today, offers a look at how low and explicit type casting looks. This will allow everyone to better understand the processes hidden in the bowels of JavaScript and will help give a reasoned answer to the question of why [1] + [2] - [3] === 9.


Explicit casts


▍ Object wrappers of primitive types


Almost all primitive types in JavaScript (the exception is nulland undefined) have object wrappers that include their values. Read more about this here . The developer has access to the designers of such objects. This fact can be used to convert values ​​of one type to values ​​of another type.

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

In the example shown here, the wrappers of variables of primitive types do not exist for long: after the job is done, the system gets rid of them.

You should pay attention to this, since the above statement does not apply to cases where the keyword is used in a similar situation new.

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

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

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

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

And this one:

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

You can verify this yourself by conducting the following experiment, which uses the Bash shell. Place the first code fragment into a file if1.js, the second into a file if2.js. Now we will do the following:

1. Compile the code in JavaScript, 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 for comparing the fourth column (here are the assembler commands) of the resulting files. It does not intentionally compare memory addresses, as they may vary.

#!/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 output the following line, which confirms the identity of the files.

"The files match"

▍ParseFloat Function


A function parseFloatworks in much the same way as a constructor Number, but it is freer on the arguments passed to it. If she encounters a character that cannot be part of a number, then she returns a value that is a number collected from the digits before this character and ignores the rest of the string passed to her.

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

▍ParseInt Function


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

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

The function parseIntcan either “guess” which number system is used to record the argument passed to it, or use the “hint” in the form of the second argument. The rules applied when using this function can be read on MDN .

This function does not work very well 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 function, toStringyou can convert values ​​of other types to strings. It should be noted that the implementation of this function in prototypes of objects of different types differs. 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 the 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 the number converted to a string (as the first argument, you can pass it the base of the number system in which the result returned by it must be presented).

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

Symbol.prototype.toString Function


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

Function Boolean.prototype.toString


This function returns trueor false.

Object.prototype.toString Function


Objects have an internal meaning [[Class]]. It is a tag representing the type of object. The function Object.prototype.toStringreturns a string of the form: `[object ${tag}]`. Here, as a tag, either standard values ​​are used (for example, “Array”, “String”, “Object”, “Date”), or the 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 . Here are a couple of examples. Here is the first one.

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

Here is the second one.

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

Here 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]'

Function Array.prototype.toString


This function, when calling it on an object of type Array, makes a call toStringfor each element of the array, collects the results in a string whose elements are separated by commas, and returns this string.

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

Implicit casting


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

▍Math 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 math operators


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

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

When converting dates to numbers, they get the Unix time corresponding to the dates.

▍ Exclamation mark


The use of an exclamation mark in expressions leads to the conclusion trueif the initial value is perceived as false, and false- for values ​​that are perceived by the system as true. As a result, an exclamation mark applied twice can be used to convert various values ​​to their corresponding logical values.

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

To ToInt32 function and bitwise OR operator


Here it is worth mentioning the function ToInt32, although this is an abstract operation (an internal mechanism that cannot be called in regular code). ToInt32converts values ​​to 32-bit signed integers .

0 | true          // 1
0 | '123'         // 123
0 | '2147483647'  // 2147483647
0 | '2147483648'  // -2147483648 (слишком большое)
0 | '-2147483648' // -2147483648
0 | '-2147483649' // 2147483647 (слишком маленькое)
0 | Infinity      // 0

Using a bitwise operator ORif one of the operands is zero and the second is a string will result in the value of the other operand not changing, but will be converted to a number.

▍Other cases of implicit casting


In the process, 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 and foo, and bar, when they are brought to a string, turn into "[object Object]". Here is what actually happens in this piece of code.

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

Implicit type conversion also happens with pattern strings . In the following example, we 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, when the types of the operands do not match, performs an implicit type conversion. Consider the following example.

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

As used herein the keyword new, fooand foo2represent a wrapper around the primitive values (and this - line 'foo'). Since the corresponding variables refer to different objects, the result is the type of comparison foo === foo2is obtained false. The operator >=performs an implicit type conversion by calling a function valueOffor both operands. Because of this, primitive values ​​are compared here, and as a result of calculating the value of the expression, it foo >= foo2turns out true.

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


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

1. In the expression [1] + [2], the operands are converted to strings, using Array.prototype.toString, after which the concatenation of what happened is performed. As a result, here we have a line "12".

  • It should be noted that, for example, an expression [1,2] + [3,4]will give a string "1,23,4";

2. When calculating the expression 12 - [3], a subtraction "3"from 12that will be performed 9.

  • Here we also consider an additional example. So, the result of evaluating the expression 12 - [3,4]will be NaN, since the system cannot implicitly lead "3,4"to a number.

Summary


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

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


Also popular now: