loading...

Coercion: Deep Dive

dvmhmdsd profile image Mohamed Saad Updated on ・7 min read

From the beginning of history of Javascript, developers thought that coercion is evil and confusing and start to ran away from it. First, let's see what's coercion really.

Definition

Coercion: it's the conversion from data type into another in javascript. It's called in other programming languages Type Casting. There's a lot of arguments around this definition, but what I liked is that (Type Casting) is the conversion in statically typed languages like Java & C++, while (coercion) is the conversion in dynamically typed languages like Javascript.

Types

  1. Explicit Coercion

It's the conversion from type into another by intention. like:

String(23); // "23"

Here we converted the number 23 into string "23" by explicitly calling String() constructor. This process is very similar to Type casting in java as we do:

int(23)

The String() constructor uses what's called Abstract operations which defined in the specification of the language, ToString. These operations is used internally only by the engine and we don't use it in our code. Also, we can use .toString() like:

let num = 23;
num.toString(); // "23"

or

23..toString(); // "23"

As you might saw, when we use the number directly without storing it in a variable, we put extra (.) before .toString(), that's because the JS engine consider the first dot as a floating point like we type: 23.5, while the second dot is related to the toString() method.

Also, when we explicitly converting from String into Number we use Number() constructor, e.g:

Number("23"); // 23

or

let str = "23";
alert(+str); // 23

Notice I used + operator for explicit coercion from string to number which is a unary operator that get a single operand and converting it into number. The Number() uses the abstract operation ToNumber which defined in the specification.

Explicitly conversion into Boolean

We can convert any non-boolean value into boolean by using surprisingly the Boolean() constructor :) e.g:

Boolean("23"); // true

or we can use !! operator

!!23; // true

Notice here we use double ! as the single ! operator used to convert the value into Boolean then reverse it. Then the second ! reverse the value again as it's reverse the reversion so the result is true.

Coercion of objects

When we explicitly convert object to any other data type, the engine uses an abstract operation which called ToPrimitive that uses valueOf() method which convert the object into its primitive value equivalent and if the valueOf() fails to get a primitive value then the ToPrimitive operation fall back into toString() method that converts the object into string which is primitive, e.g:

var a = {
    valueOf: function () {
        return "33";
    }
}

Number(a); // 33

var a = {
    toString: function () {
        return "33";
    }
}

Number(a); // 33

Number([]); // 0

In the last example, the array is converted into number which produces 0.

  1. Implicit Coercion

The second type of coercion is implicit coercion which is the conversion of type without intentionally convert it as it's hidden, non-obvious. This type of conversion confuse a lot of developers and make them avoid it and consider it a bug in javascript.

As the most of developers see implicit coercion evil, I see it from a different perspective as it's used to reduce a lot of boilerplate and details that's unnecessary. As Kyle Sympthon said in his book YDKJS - Types & Grammar : "Don't throw the baby out with the bathwater" because the developers see implicit coercion evil they throw it away just to be safe, which is wrong.

Let's see how we can implicitly convert from type to another.

A number is implicitly converted into string by putting it in concatenation expression with a string, e.g:

"2" + 2; // "22"
2 + "2"; // "22"

Here the number 2 converted into string by concatenate it with string "2". Let's consider this example:

[2, 4] + [3, 6]; // "2, 43, 6"

Wait! What!, confusing hah! No, Not really.

While none of two operand are string the operation produces string. As the ES5 Specification said that when "+" operator receive an object it uses ToPrimitive operation which convert an object which is non-primitive into primitive, but which type? Number? or String?

Actually, the ToPrimitive operation uses under the hood the ToNumber operation which fails to produce a number then it falls back into toString method that convert the first array into string ("2, 4") and the second one into ("3, 6") then the operation becomes a normal string concatenation:

"2, 4" + "3, 6"; // "2, 43, 6"

Obvious, hah!

A string is converted into number by adding the string in mathematical operation, e.g:

let a = "42";

a - 0; // 42
Implicitly converting from any value into Boolean

This's the most confusing part in implicit coercion. There's cases where implicit coercion to boolean happens:

  • Expression in if() statement
  • The test expression in for(;..;)
  • The test expression in while() loop
  • The test expression in ternary operators
  • The left hand side in logical operators, ||, &&
let num = 2;

if (num) {
    alert("It's true !"); // It's true !
}

Here num implicitly converted into Boolean "true", then the test run and the alert fires because the number (2) is a truthy value which means it converted into true when put in context of boolean.

You may notice that I said "The left hand side in logical operators", the truth is these operators is not working like you expect they do similar to other languages like PHP / Java, actually it works differently, so, how is it working ? let's take an example:

let b = 23;
let c = "Hi";

b && c; // "Hi"

b || c; // 23 

So, in the first expression, the test goes for the left hand side of the (&&) operator, convert it to boolean - which is truthy - then, return the right hand side. In the second expression the test goes for the left hand side, convert it to boolean - which is truthy also - then return it and doesn't go to the right hand side. This process is called "Short circuit evaluation".

The use case for this concept is when you want to return a value depending on truthiness of another value like we do in react, so we use the (&&), also when you want to return a value and provide a fall back when the first value is falsy.

// here we check if the array is empty, so don't return any thing or return a paragraphs containing the items of the array
{ arr.length > 0 && arr.map(item => {
    return <p>item</p>;
}) }

// here if the foo() returned undefined or null or any falsy values, the "no value returned" will be returned or the value returned from foo() will be returned either
{ foo() || "no value returned" }
== vs. ===
2 == "2"; // true

2 === "2"; // false

Most of us will respond to this title: "== compares only values while === compares both types and values", actually this's completely wrong !

Both of them compares types and values, but the difference is if one of them permit coercion or not. In the first expression we'll notice that == operator permit the coercion from string to number so the result was true, while in the second expression the === doesn't permit coercion so the value was false.

Which is better ?

Other developers will argue wether is better and their teammates will respond: "of course === because it's much faster than ==", this's also wrong !

Um, yeah, there's a bit difference in performance but it's not considered because they are very close to each other, so, the final answer is: it doesn't matter which is faster, if you want to permit coercion, use ==, otherwise use ===, simply as that.

Comparing non-boolean to boolean

The most risky and confusing example that a lot if developers fall in is when comparing any value to true or false. Let's consider this example:

1 == true; // true

"5" == true; // false

What ! How come !

The ES5 Spec said:

  • if one of the two operand (x) is boolean return ToNumber(x) and compare them to each other.

So, when we compare 1 to true the boolean value "true" implicitly converted into number which is 1, then 1 == 1 is obviously true, while in the second example when we compare "5" to true, the boolean value implicitly converted into number which is 1 and the "5" converted into number which is 5, so, 5 == 1 is obviously false.

  • If either side of the comparison can be implicitly converted into true / false, Don't never ever use ==.
Comparing non-objects to objects

If an object is compared to a primitive value the ES5 Spec said:

  • If any one of the two operands (x) is object and the other is primitive, return ToPrimitive(x) and compare them to each other.

Let's consider this example:

2 == [2]; // true

Here the [2] converted into its primitive value which is 2, then compare it to 2 which is obviously true.

Simple, hah!

Let's see this example:

false == []; // true

[] == {}; // false

In the first example, false is converted into number - as we said above - which is 0 and [] converted into number by ToPrimitive operation which is 0 so the result was true, while in the second example, [] converted into number by ToPrimitive operation which is 0 and {} converted into its primitive value which is NaN so the result is false, as NaN never equal to itself or any other value.

Let's see this final example:

[] == ![]; // true

Whaaaaaaaaat !

This's completely crazy !

Let's take it from right hand side, the ![] - by remember the rules of converting objects - the [] is first converted into boolean which is true then negate it, so, the result of ![] is false, then compare true to [], [] == false, we saw this before and the result was true.

  • If either side of comparison is object, Don't never ever use ==.

Conclusion

Coercion was - for many developers - an evil and confusing concept, it's the conversion from data type into another, it has 2 types, first is the explicit coercion, which is the intentionally converting from type into another, and implicit coercion, which is the hidden converting from type into another.

Implicit Coercion hides unnecessary details and simplify the implicitly.

When comparing any value to Boolean or to Object consider not using == operator as the coercion here make confusing and unpredicted results.

Sources

  1. You Don't Know Javascript - Types & Grammar

  2. MDN Reference

Posted on Nov 23 '19 by:

dvmhmdsd profile

Mohamed Saad

@dvmhmdsd

A JavaScript developer specialized in front-end topics.

Discussion

markdown guide