DEV Community

Cover image for Javascript Coercions (Conversions)
Arsalan Khattak
Arsalan Khattak

Posted on

Javascript Coercions (Conversions)

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?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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] == ","
[] == ""
[[],[],[]] == ",,"
Enter fullscreen mode Exit fullscreen mode

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 or undefined in an array is converted to an empty string ( [1, null, 2] will be converted to 1,,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(){}"
Enter fullscreen mode Exit fullscreen mode

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.

to string

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
to 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
Enter fullscreen mode Exit fullscreen mode

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:
to boolean
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();
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
southglitch profile image
SouthGlitch

I still don't get why [] == ![] is true.
[] is evalueted as an empty string, from there is converted to a 0, 0 should be equal to false, !false is true. So ![] shouldn't be true?

Collapse
 
khattakdev profile image
Arsalan Khattak

Here's how it works

[] == ![]
0 == 0   // (Number([]) and Number(![]) both are equal to 0)
Enter fullscreen mode Exit fullscreen mode

Once both are 0,
Double Equals
And strict equality is pretty straight forward, if the values and types are the same, returns true