Javascript Coercions (Conversions)
Arsalan Khattak
Posted on January 6, 2021
This blog is about Javascript Coercion - converting one type to another. This blog post covers the hidden steps/algorithms that the Javascript engine takes to convert to another type.
Motivation
The motivation behind this blog is that many developers have no idea how Coercions or conversion works in Javascript. Therefore, they consider these value conversions something evil but it’s actually not. Many developers I talked to, think that these conversions have bugs. I don’t think you can call them bugs but inconsistencies.
Introduction
Let's start with a few weird cases of conversions.
[] == 0 // true
[] == ![] // true, WHY?
NaN == NaN // false, weird
1 < 2 < 3 // true, cool
3 > 2 > 1 // false, wait what?
For some of you, some of these examples might look okay to e.g. [] == 0
or NaN == NaN
but the other might looks weird. Once you know the algorithm that the javascript engine uses to convert these types, this will look normal.
Abstract Operations
There are certain sets of operations known as Abstract Operations that help in converting values from one type to another.
Now keep in mind that these operations are not actually available in Javascript, you can't call them just like a normal function. They are only called by Javascript Engine.
ToPrimitive
This operation converts any non Primitive value to a primitive value i.e. either to a number or to a string, depending on a hint, that is passed to this method toPrimitive(object, hint)
. For example, if there is some string-based operation on a non-primitive value, it will send String as a hint.
This method accepts two arguments (object, hint)
. The first one is the non-primitive value that needs to be converted. The second one the hint. The hint is either string
or number
.
There are two more abstract operations, one of them is called depending on the hint. Those operations are
valueof()
toString()
If the hint is number
, toPrimitive
will call valueof
operation, which will try to convert the object to a number, in case if it fails, it will go for toString
.
If the hint is string
, toPrimitive
will call toString
operation, which will try to convert the object to a string
, in case if it fails, it will go for valueOf
Converting to String
Starting with strings, let's take a look at some easy examples of converting to Strings.
undefined == "undefined"
null == "null"
false == "false"
42 == "42"
0 == "0"
NaN == "NaN"
-0 == "0" // Edge Case
All the primitive types, when converted to a string, are just wrapped with double-quotes. -0
is a special case, which is converted to 0.
(💡 Yes, -0 actually exist in JavaScript )
Now let's take a look at some non-primitive to primitive (string examples)
[1, 2, 3] == "1,2,3"
[,,,] == ",,,"
[null, undefined] == ","
[] == ""
[[],[],[]] == ",,"
Some complex examples, may or may not looks normal to you (depending on your experience) but don't worry we will talk about the actual algorithm in just a while.
- An array with primitive values, when converted to a string, is that same array, joined together with commas.
- An array with an empty index is converted to a combination of commas.
-
null
orundefined
in an array is converted to an empty string ([1, null, 2]
will be converted to1,,2
) - An Empty Array always becomes an empty string.
- An empty nested array also becomes an empty string.
Some more examples
{ } == "[object Object]" // Empty Object
{ a: 2 } == "[object Object]"
function() { } == "function(){}"
An object (either empty or not) when converted to String, it is [object Object]
. Functions, when converted to a string, just wraps themselves in double-quotes.
Okay, now let's take a look at the algorithm that the Javascript engine uses to convert a value to a string type.
So the algorithm is
- undefined will be
"undefined"
- null will be "null"
- boolean will be "true" or "false"
- Number when passed, will be wrapped in double-quotes.
- So on...
The Object will use the toPrimitive
abstract operation with hint string
. The returning value will be then again passed to this toString
and it will return you the result.
Converting to Number
undefined == NaN
null == 0
True == 1
False == 0
"0" == 0
"-0" == 0
"" == 0
Some weird cases are undefined
is NaN
but null
is 0, "-0" is -0 but -0 is "-0" (previous example, converting to string). Well, these are just inconsistencies.
Take a look at a few more non-primitive examples.
[""] == 0
[[[]]] == 0
[null] == 0
[undefined] == 0
[1,2] == NaN
Almost all of them converts to 0, except for the last example.
To understanding the working, keep in mind two rules of Javascript
- An empty string, when converted to a number will always be 0.
- An empty array when converted to a string, will always be an empty string.
Now what happens here
-
[""]
is converted to empty string (""
), which is then converted to 0. -
[[[]]]
nested empty array is converted to an empty string, which is then converted to 0. -
[undefined]
and[null]
is converted to an empty string, which is then converted to 0. (Null and Undefined always becomes an empty string.[undefined, null]
when converted, becomes","
) - Last one is
NaN
because[1,2]
is when converted, it becomes"1,2"
which is then converted to a number (NaN
, because of the comma )
Here's the algorithm that the JavaScript engine uses to convert any type to a Number.
The algorithm for converting an object to a Number is the same as converting any object to a string with the difference of hint, which will be Number in this case.
Converting to Boolean
// Falsey Truthy
0, -0 // Everything Else
""
false
undefined
null
NaN
Booleans are easy. All the values that are mentioned in the list of Falsey
are false when you convert them to boolean and everything else (an object, a non-empty string, numbers greater than 1, etc) will be true when converted to boolean. These values will always behave the same under any circumstances. Just memorize this list and you'll be able to write bugs free code when converting to a boolean.
Here's what the docs say:
Pretty straight forward, isn't it?
Coercions
Double Equals (==) - Good or Bad?
I'm sure you've seen a lot of blog posts and articles where the author discouraged you not to use double equals. These blogs author wants you to always use triple equals ===
. The reason they give is that ==
do the coercion which is something evil.
Well, I disagree with this. Coercion is evil when you don't know anything about it and that's why you end up having buggy code (which is not actually buggy). Instead of avoiding ==
, whenever possible, you must familiarize yourself more with the arguments and values type.
Now I do not say to always use ==
and never use ===
and I also disagree with what those blog articles suggest to you.
Use a suitable one based on the scenario. You actually can not ignore ==
at all. In fact, you are already using it in your code but you don't know. We all do coercions, but we don't know that.
Implicit Coercion
let arr = [`1,2,3,4];
while (arr.length) {
arr.pop();
}
The above code snippet will execute until the length of the array is 0. Here we've used implicit coercion (the double equals). HOW?
So we have an array arr
and we get its length by arr.length
which returns 4. Notice we used arr.length
as a condition of while(){}
which is actually converting the number to a boolean. Now as you studied earlier, any number greater than 0 is true, when converted to boolean, so this returns true until the length becomes 0.
Another example:
var userAge = document.querySelector(".ageInput");
function doubleAge(age) {
return age * age;
}
doubleAge(userAge.nodeValue);
Here again, we did implicit coercion(the double equals). The userAge
is getting a value from the HTML input element, so it's of type string, but the return age * age
is actually doing a multiplication, here the age is converted to number for multiplication.
One more:
var userAge = 21;
console.log(`Your age is ${userAge}`);
Here the type of userAge
is an integer but when passed as an argument in console.log
it is implicitly converted to a string.
Conclusion
On taking a look at the specs, we can conclude that
- JavaScript has some edge cases, which can be avoided by reading the documentation
- It's better to understand your variable types rather than using triple equals (===) everywhere
- We are using double equals (==) in our code unintentionally
Posted on January 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.