Implicit type conversion in JavaScript. How much will it be! + [] + [] +! []?
- Transfer
Type conversion is the process of converting values from one type to another (for example, a string into a number, an object into a logical value, and so on). Any type in JavaScript, whether it is a primitive type, or an object, can be converted to another type. Recall that primitive data types are JS
This article focuses on the features of type casting mechanisms in JavaScript. We begin it with a list of expressions whose calculation results may look completely unexpected. You can test yourself by trying to find the meanings of these expressions without looking at the end of the article, where their analysis will be given.
Here is a list of interesting expressions that we just talked about:
It is full of things that look more than strange, but they work without problems in JS, using implicit type conversion. In the vast majority of cases, implicit type conversion in JS is best avoided. View this list as an exercise to test your knowledge of how typecasting works in JavaScript. If there is nothing new for you here, take a look at wtfjs.com .
JavaScript is full of weirdness.
Here is a page with a table that shows the behavior of the lax equality operator in JavaScript
Type conversion can be explicit and implicit. When a developer expresses an intention to convert a value of one type into a value of another type, writing it accordingly in code, say, in the form
Since JavaScript is a weakly typed language, values can be converted between different types automatically. This is called implicit type conversion. Usually this happens when the expression is used in the values of different types, such as
There is an operator that does not cause an implicit type conversion - is the operator of strict equality
Implicit type conversion is a double-edged sword: it is a source of confusion and errors, but it is also a useful mechanism that allows you to write less code without losing its readability.
The first feature of working with types in JS that you need to know about is that there are only three types of conversions:
The second JS feature to consider is that the conversion logic for primitive types and objects works differently, but primitives and objects can be converted to these three types. Let's start with primitive data types.
In order to explicitly convert a value to a string, you can use the function
All primitive types are converted to strings in a completely natural and expected way:
In the case of a type,
In order to explicitly convert the value to a logical type, use the function
Note that operators, like
Since when converting a value to a logical type, only two results are possible -
Any value not included in this list is converted to
An explicit conversion to a numeric type is performed using a function
The implicit conversion of a value to a numeric type is a more complicated topic, since it is used, perhaps more often, than conversion to a string or a logical value. Namely, the conversion to type is
Here's how primitive values are converted to numbers:
When converting strings to numbers, the system first trims spaces, as well as the
Values
Type values
Here are two special rules to remember:
When applying the operator
Meaning is
So, we looked at type conversion for primitive values. Everything is pretty simple here. When it comes to objects, and the system encounters expressions like
The simplest is conversion to a boolean value: any value that is not a primitive is always implicitly converted to
Objects are converted to primitive values using an internal method
Here is the pseudo implementation of the method
The
Both when converting to a number, and when converting to a string, two methods of the object passed are used
In general, the operation of the algorithm is as follows:
When converting to a number,
Most built-in types do not have a method
Different operators can either convert to a number or convert to a string using the parameter
Here is an example of
Standard methods
Pay attention to what
In ES5, it is permissible to change the logic of converting an object to a primitive value by overriding the
In ES6, you can go even further and completely replace the internal mechanism
Armed with theory, let us return to the expressions given at the beginning of the material. Here are the results of calculating these expressions:
Let us examine each of these examples.
An operator
The arithmetic division operator,,
The operator
The expression is
The comparison operator
A unary operator
The operator
An operator
The operator
The operator
The operator
In the second step, the value of the expression is calculated
Logical operators
Type conversion is not required since both operands are of the same type. Since the operator
All operands are not primitive values, so the operator
This example is best explained step by step in accordance with the order of operations.
The operator
The operator
Type conversion is one of the basic JavaScript mechanisms, knowledge of which is the basis of productive work. We hope that today's material helped those who were not very well versed in implicit type conversion, put everything in its place, and for those who confidently, without first peeping anywhere, were able to solve the "introductory task", let me recall some interesting case from their practice.
Dear readers! And in your practice, it happened that confusion with implicit type conversion in JavaScript leads to mysterious errors?
Number
, String
, Boolean
, Null
, Undefined
. Type added to this list in ES6Symbol
, which behaves quite differently from other types. Explicit type conversion is a simple and straightforward process, but everything changes when it comes to implicit type conversion. Here, what happens in JavaScript, some consider it strange or illogical, although, of course, if you look at the standards, it becomes clear that all these "oddities" are features of the language. Be that as it may, any JS developer periodically has to deal with implicit type conversion, and besides, tricky questions about type conversion may well come up at the interview.This article focuses on the features of type casting mechanisms in JavaScript. We begin it with a list of expressions whose calculation results may look completely unexpected. You can test yourself by trying to find the meanings of these expressions without looking at the end of the article, where their analysis will be given.
check yourself
Here is a list of interesting expressions that we just talked about:
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
0 || "0" && {}
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0
It is full of things that look more than strange, but they work without problems in JS, using implicit type conversion. In the vast majority of cases, implicit type conversion in JS is best avoided. View this list as an exercise to test your knowledge of how typecasting works in JavaScript. If there is nothing new for you here, take a look at wtfjs.com .
JavaScript is full of weirdness.
Here is a page with a table that shows the behavior of the lax equality operator in JavaScript
==
, when comparing values of different types. The implicit type conversion performed by the operator ==
makes this table much less understandable and logical than, say, a table for the strict equality operator,===
The link to which can be found on the above page. It is ==
almost impossible to memorize a comparison table for an operator . But you don’t need to remember all this - just master the principles of type conversion used in JavaScript.Implicit type conversion and explicit type conversion
Type conversion can be explicit and implicit. When a developer expresses an intention to convert a value of one type into a value of another type, writing it accordingly in code, say, in the form
Number(value)
, this is called explicit type conversion (or explicit type conversion). Since JavaScript is a weakly typed language, values can be converted between different types automatically. This is called implicit type conversion. Usually this happens when the expression is used in the values of different types, such as
1 == null
, 2/’5'
, null + new Date()
. Implicit type conversion can also be caused by the context of an expression, such if (value) {…}
as where it is value
implicitly cast to a logical data type.There is an operator that does not cause an implicit type conversion - is the operator of strict equality
===
. The non-strict equality operator ==
, on the other hand, performs the comparison operation, and, if necessary, performs implicit type conversion. Implicit type conversion is a double-edged sword: it is a source of confusion and errors, but it is also a useful mechanism that allows you to write less code without losing its readability.
Three types of type conversion
The first feature of working with types in JS that you need to know about is that there are only three types of conversions:
- To string (
String
) - In boolean value (
Boolean
) - In number (
Number
)
The second JS feature to consider is that the conversion logic for primitive types and objects works differently, but primitives and objects can be converted to these three types. Let's start with primitive data types.
Primitive Data Types
▍ Converting to String
In order to explicitly convert a value to a string, you can use the function
String()
. Implicit conversion causes the use of the usual addition operator,, +
with two operands, if one of them is a string:String(123) // явное преобразование
123 + '' // неявное преобразование
All primitive types are converted to strings in a completely natural and expected way:
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
In the case of a type,
Symbol
things get a little more complicated, since values of this type can be converted to a string type only explicitly. Here you can read more about the conversion rules of type Symbol.String(Symbol('my symbol')) // 'Symbol(my symbol)'
'' + Symbol('my symbol') // ошибка TypeError
▍ Convert to Boolean type
In order to explicitly convert the value to a logical type, use the function
Boolean()
. Implicit conversion occurs in a logical context, or is invoked by logical operators ( ||
&&
!
).Boolean(2) // явное преобразование
if (2) { ... } // неявное преобразование в логическом контексте
!!2 // неявное преобразование логическим оператором
2 || 'hello' // неявное преобразование логическим оператором
Note that operators, like
||
, &&
perform conversion of values to a logical type for internal purposes, and return the values of the original operands, even if they are not logical.// это выражение возвращает число 123, а не true
// 'hello' и 123 неявно преобразуются к логическому типу при работе оператора && для вычисления значения выражения
let x = 'hello' && 123; // x === 123
Since when converting a value to a logical type, only two results are possible -
true
or false
, it is easiest to master this type of transformation, remembering those expressions that produce false
:Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
Any value not included in this list is converted to
true
, including objects, functions, arrays, dates, as well as user-defined types. Type values are Symbol
also converted to true
. Empty objects and empty arrays are also converted to true
:Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
▍ Converting to Number
An explicit conversion to a numeric type is performed using a function
Number()
— that is, by the same principle as that used for Boolean
and types String
. The implicit conversion of a value to a numeric type is a more complicated topic, since it is used, perhaps more often, than conversion to a string or a logical value. Namely, the conversion to type is
Number
performed by the following operators:- Comparison Operators (
>
,<
,<=
,>=
). - Bitwise Operators (
|
,&
,^
,~
). - Arithmetic operators (
-
,+
,*
,/
,%
). Note that an operator+
with two operands does not cause an implicit conversion to a numeric type if at least one operator is a string. - Unary operator
+
. - Non-strict equality operator
==
(as well!=
). Note that the operator==
does not implicitly convert to a number if both operands are strings.
Number('123') // явное преобразование
+'123' // неявное преобразование
123 != '456' // неявное преобразование
4 > '5' // неявное преобразование
5/null // неявное преобразование
true | 0 // неявное преобразование
Here's how primitive values are converted to numbers:
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12.34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
When converting strings to numbers, the system first trims spaces, as well as the
\n
and characters \t
at the beginning or end of the string, and returns NaN
if the resulting string is not a real number. If the string is empty, it is returned 0
. Values
null
and are undefined
processed differently: null
converted to 0
, while undefined
converted to NaN
. Type values
Symbol
cannot be converted to a number either explicitly or implicitly. Moreover, when attempting such a conversion, an error is thrown TypeError
. One would expect that this would cause the conversion of the type value Symbol
to NaN
, as is the case withundefined
but this does not happen. Details about the rules for converting type values Symbol
can be found on MDN .Number(Symbol('my symbol')) // Ошибка TypeError
+Symbol('123') // Ошибка TypeError
Here are two special rules to remember:
When applying the operator
==
to null
or undefined
converting to a number is not performed. The value null
is equal to only null
or undefined
and not equal to anything else.null == 0 // false, null не преобразуется в 0
null == null // true
undefined == undefined // true
null == undefined // true
Meaning is
NaN
not equal to anything, including yourself. In the following example, if the value is not equal to itself, then we are dealing withNaN
if (value !== value) { console.log("we're dealing with NaN here") }
Type conversion for objects
So, we looked at type conversion for primitive values. Everything is pretty simple here. When it comes to objects, and the system encounters expressions like
[1] + [2,3]
, first it needs to convert the object to a primitive value, which is then converted to the final type. When working with objects, we recall that there are also only three directions of conversion: to a number, to a string, and to a logical value. The simplest is conversion to a boolean value: any value that is not a primitive is always implicitly converted to
true
, this is also true for empty objects and arrays. Objects are converted to primitive values using an internal method
[[ToPrimitive]]
that is responsible for converting to a numerical type and converting to a string.Here is the pseudo implementation of the method
[[ToPrimitive]]
:function ToPrimitive(input, preferredType){
switch (preferredType){
case Number:
return toNumber(input);
break;
case String:
return toString(input);
break
default:
return toNumber(input);
}
function isPrimitive(value){
return value !== Object(value);
}
function toString(){
if (isPrimitive(input.toString())) return input.toString();
if (isPrimitive(input.valueOf())) return input.valueOf();
throw new TypeError();
}
function toNumber(){
if (isPrimitive(input.valueOf())) return input.valueOf();
if (isPrimitive(input.toString())) return input.toString();
throw new TypeError();
}
}
The
[[ToPrimitive]]
input value and the preferred type to which it must be converted are passed to the method : Number
or String
. In this case, the argument is preferredType
optional. Both when converting to a number, and when converting to a string, two methods of the object passed are used
[[ToPrimitive]]
: this valueOf
and toString
. Both methods are declared in Object.prototype
, and, thus, available for any kind based on Object
, for example - it is Date
, Array
and so on. In general, the operation of the algorithm is as follows:
- If the input value is a primitive, do nothing and return it.
- Call
input.toString()
if the result is a value of a primitive type - return it. - Call
input.valueOf()
if the result is a value of a primitive type - return it. - If neither
input.toString()
, norinput.valueOf()
give a primitive value - give an errorTypeError
.
When converting to a number,
valueOf
(3) is first called ; if the result cannot be obtained, toString
(2) is called . When converting to a string, the reverse sequence of actions is used - first toString
(2) is called , and in case of failure, valueOf
(3) is called. Most built-in types do not have a method
valueOf
, or have valueOf
, which returns the object itself for which it is called ( this
), so this value is ignored, since it is not a primitive. That is why converting to a number and to a string can work the same way - both come down to a call toString()
. Different operators can either convert to a number or convert to a string using the parameter
preferredType
. But there are two exceptions: the non-strict equality ==
operator and the operator +
with two operands cause a default conversion ( preferredType
not specified or set to a value default
). In this case, most of the built-in types are considered, as a standard variant of behavior, conversion to a number, except for the type Date
that performs the conversion of the object to a string. Here is an example of
Date
type conversion behavior :let d = new Date();
// получение строкового представления
let str = d.toString(); // 'Wed Jan 17 2018 16:15:42'
// получение числового представления, то есть - числа миллисекунд с начала эпохи Unix
let num = d.valueOf(); // 1516198542525
// сравнение со строковым представлением
// получаем true так как d конвертируется в ту же строку
console.log(d == str); // true
// сравнение с числовым представлением
// получаем false, так как d не преобразуется в число с помощью valueOf()
console.log(d == num); // false
// Результат 'Wed Jan 17 2018 16:15:42Wed Jan 17 2018 16:15:42'
// '+', так же, как и '==', вызывает режим преобразования по умолчанию
console.log(d + d);
// Результат 0, так как оператор '-' явно вызывает преобразование в число, а не преобразование по умолчанию
console.log(d - d);
Standard methods
toString()
and valueOf()
can be overridden in order to intervene in the logic of transforming an object into primitive values.var obj = {
prop: 101,
toString(){
return 'Prop: ' + this.prop;
},
valueOf() {
return this.prop;
}
};
console.log(String(obj)); // 'Prop: 101'
console.log(obj + '') // '101'
console.log(+obj); // 101
console.log(obj > 100); // true
Pay attention to what
obj + ‘’
returns ‘101’
as a string. The operator +
calls the standard conversion mode. As already mentioned, it Object
considers casting to a number as the default conversion, therefore it uses the method first valueOf()
and not toString()
.Symbol.toPrimitive ES6 Method
In ES5, it is permissible to change the logic of converting an object to a primitive value by overriding the
toString
and methods valueOf
. In ES6, you can go even further and completely replace the internal mechanism
[[ToPrimitive]]
by implementing the object method [Symbol.toPrimtive]
.class Disk {
constructor(capacity){
this.capacity = capacity;
}
[Symbol.toPrimitive](hint){
switch (hint) {
case 'string':
return 'Capacity: ' + this.capacity + ' bytes';
case 'number':
// преобразование в KiB
return this.capacity / 1024;
default:
// считаем преобразование в число стандартным
return this.capacity / 1024;
}
}
}
// 1MiB диск
let disk = new Disk(1024 * 1024);
console.log(String(disk)) // Capacity: 1048576 bytes
console.log(disk + '') // '1024'
console.log(+disk); // 1024
console.log(disk > 1000); // true
Analysis of examples
Armed with theory, let us return to the expressions given at the beginning of the material. Here are the results of calculating these expressions:
true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1] > null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!!"false" == !!"true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
0 || "0" && {} // {}
[1,2,3] == [1,2,3] // false
{}+[]+{}+[1] // '0[object Object]1'
!+[]+[]+![] // 'truefalse'
new Date(0) - 0 // 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0'
Let us examine each of these examples.
▍true + false
An operator
+
with two operands causes a conversion to a number for true
and false
:true + false
==> 1 + 0
==> 1
▍12 / '6'
The arithmetic division operator,,
/
causes a conversion to a number for a string '6'
:12 / '6'
==> 12 / 6
==>> 2
▍ "number" + 15 + 3
The operator
+
has associativity from left to right, so the expression "number" + 15
is executed first. Since one of the operands is a string, the operator +
invokes a conversion to a string for a number 15
. In the second step, the calculation of the expression is "number15" + 3
processed in the same way:"number" + 15 + 3
==> "number15" + 3
==> "number153"
▍15 + 3 + "number"
The expression is
15 + 3
evaluated first. There is absolutely no need for type conversion, since both operands are numbers. At the second step, the value of the expression is calculated 18 + 'number'
, and since one of the operands is a string, conversion to a string is called.15 + 3 + "number"
==> 18 + "number"
==> "18number"
▍ [1]> null
The comparison operator
>
performs a numerical comparison [1]
and null
:[1] > null
==> '1' > 0
==> 1 > 0
==> true
▍ "foo" + + "bar"
A unary operator
+
has higher priority than a regular operator +
. As a result, the expression is +'bar'
evaluated first. Unary +
causes a string to 'bar'
convert to a number. Since the string is not a valid number, the result is NaN
. In the second step, the value of the expression is calculated 'foo' + NaN
."foo" + + "bar"
==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
▍'true '== true and false ==' false '
The operator
==
calls the conversion to number, the string is 'true'
converted to NaN
, the logical value is true
converted to 1
.'true' == true
==> NaN == 1
==> false
false == 'false'
==> 0 == NaN
==> false
▍null == ''
An operator
==
usually calls a conversion to a number, but this is not the case with a value null
. The value null
is only null
or or undefined
nothing more.null == ''
==> false
▍ !! "false" == !! "true"
The operator
!!
converts the string 'true'
and 'false'
to the logic true
, since they are non-empty strings. Then the operator ==
simply checks the equality of two logical values true
without type conversion.!!"false" == !!"true"
==> true == true
==> true
▍ ['x'] == 'x'
The operator
==
invokes a conversion to a numeric type for arrays. The object method Array.valueOf()
returns the array itself, and this value is ignored, since it is not a primitive. An array method toString()
converts an array ['x']
to a string 'x'
.['x'] == 'x'
==> 'x' == 'x'
==> true
▍ [] + null + 1
The operator
+
calls a conversion to a number for an empty array []
. The object's method is Array
valueOf()
ignored because it returns the array itself, which is not a primitive. The array method toString()
returns an empty string. In the second step, the value of the expression is calculated
'' + null + 1
.[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'
▍0 || "0" && {}
Logical operators
||
and &&
during operation lead to the logical value of the operand type, but return source operands (which are of a type different from the logical). The value is 0
false, and the value is '0'
true, since it is a non-empty string. An empty object {}
is also converted to a true value.0 || "0" && {}
==> (0 || "0") && {}
==> (false || true) && true // внутреннее преобразование
==> "0" && {}
==> true && true // внутреннее преобразование
==> {}
▍ [1,2,3] == [1,2,3]
Type conversion is not required since both operands are of the same type. Since the operator
==
checks for equality of references to objects (and not whether the objects contain the same values) and two arrays are two different objects, the result will be returned false
.[1,2,3] == [1,2,3]
==> false
▍ {} + [] + {} + [1]
All operands are not primitive values, so the operator
+
begins with the leftmost one and causes its conversion to a number. The method is valueOf
for types Object
and Array
these objects themselves return, therefore this value is ignored. The method is toString()
used as a fallback. The trick here is that the first pair of curly braces is {}
not considered an object literal, it is perceived as a block of code that is ignored. The calculation starts with the next expression, +[]
which is converted to an empty string through the method toString()
, and then to 0.{}+[]+{}+[1]
==> +[]+{}+[1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'
▍! + [] + [] +! []
This example is best explained step by step in accordance with the order of operations.
!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
▍new Date (0) - 0
The operator
-
calls a conversion to a number for an object of type Date
. The method Date.valueOf()
returns the number of milliseconds since the beginning of the Unix era.new Date(0) - 0
==> 0 - 0
==> 0
▍new Date (0) + 0
The operator
+
invokes the default transformation. Type objects Data
consider conversion to string to be such a conversion; as a result, the method is used toString()
, and not valueOf()
.new Date(0) + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
Summary
Type conversion is one of the basic JavaScript mechanisms, knowledge of which is the basis of productive work. We hope that today's material helped those who were not very well versed in implicit type conversion, put everything in its place, and for those who confidently, without first peeping anywhere, were able to solve the "introductory task", let me recall some interesting case from their practice.
Dear readers! And in your practice, it happened that confusion with implicit type conversion in JavaScript leads to mysterious errors?