JavaScript coercions

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.

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.

JavaScript Coercions

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

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.

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. argument type

So the algorithm is

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

Now what happens here

Here’s the algorithm that the JavaScript engine uses to convert any type to a Number. argument types

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
0, -0;
("");
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: arguments type

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