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
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.
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
You Don't Know Javascript - Types & Grammar
Top comments (0)