== vs === JavaScript: Double Equals and Coercion
The == (double equals or loose equality) operator is an interesting operator. Many avoid it because they don’t know how it works. But that’s not a good reason to avoid it; a better reason would be knowing how it works, and knowing why you want to avoid it. And then you will know why you prefer the === (triple equals) operator for comparisons.
After reading this article, you will know all you need to know about coercion and double equals in JavaScript. Let’s get started!
Double Equals
The double equals operator coerce the operands if their types are not the same, otherwise, it will use the triple equals operator. So, 2 == 3
is the same as 2 === 3
because both 2
and 3
are of the number
type. But if the types are not the same, JavaScript will try to coerce either one or both of the operands. For example, if you compare 2 == "3"
, you are comparing number 2 with a string whose value is 3. In this case, JavaScript will coerce string 3 to number 3, and then it will call 2 === 3
which will result false.
Now, that we know the basics of the double equals operator, let’s dive deeper and learn how the operator decides what to do.
The Abstract Equality Comparison
(Side note: You can read the complete information about abstract equality comparison in the ECMAScript® 2015 Language Specification)
Assume that we are comparing x and y, where x and y could be of any type:
Start: Check if the types are the same. If the types are the same, JavaScript will call triple equals and you are done.
But if the types are not the same, JavaScript will follow another path. In this path, JavaScript will try to figure out what to coerce. These are the steps it will execute:
a. First check if we are comparing null
and undefined
. If we are, return true. If not:
b. Check if we are comparing a string
with a number
. If we are, coerce the string
value to number and call the double equals and go back to the very first step. Eg:
2 == "3"
↓ coerce "3" to number 3.
2 == 3 calling double equals again, and start over
...
If not:
c. Check if we are comparing a boolean
with something else. If we are, coerce the boolean
to a number and call double equals and start over. Eg:
true == "3"
↓ coerce true to number 1.
1 == "3" start over again.
...
If not:
d. Check if we are comparing an object
with a number
, string
, or a symbol
. If we are, coerce the object
to a primitive, call double equals on the result and start over again. Eg:
{ a: 'hello'} == "5"
↓ coerce the object to a primitive
"[object Object]" == "5" and start over again
...
Another example:
[1,2,3] == "5"
↓ coerce object to a primitive
"1,2,3" == "5" and start over again
...
(I’ll explain how the objects get coerced to primitives in a second, but for now bare with me.)
If not, we don’t have anything else to check. If none of the steps above matches, return false at the end.
Here is the complete picture:
You can click here to see the larger version.
Abstract Operators:
Coercing Objects to Primitives
I promised to explain how objects get coerced to primitives. This is how it works:
First by default, call the valueOf
method of the object. If valueOf
returns a primitive, use that.
Otherwise call the toString
method of the object. If toString
returns a primitive, use that
otherwise throw an error.
Note 1: These steps happen by default. If it is hinted to prefer string
, JavaScript will call toString
first, and then valueOf
. By default JavaScript is hinted to prefer number
, so it will call valueOf
first, and then toString
.
Note 2: When coercing a Date
object to a primitive, JavaScript is hinted to prefer string
. So it will call toString
first and then valueOf
. In all the other cases, the default preferred type hint is number
.
Let’s look at some examples:
Example 1:
// coercing an empty object to a primitive
var x = {};
x.valueOf(); // -> {} : not a primitive, call toString now.
x.toString(); // -> "[object Object]": string is a primitive, I like it!
In the example above, when x
is coerced to a primitive, the result would be the funny looking string: "[object Object]"
Example 2:
// coercing an object with custom `valueOf`
var x = {name: 'Tom'};
x.valueOf = function () {return 5;};
x.valueOf(); // -> 5 : number primitive, I liked it!
In the example above, when x
is coerced to a primitive, the result would be the number 5.
Example 3:
// coercing an array of numbers to a primitive
var x = [1,2,3];
x.valueOf(); // -> [1,2,3] : not a primitive, call toString now.
x.toString(); // -> "1,2,3" : string is a primitive, I like it!
In the example above, when x
is coerced to a primitive, the result would be the string: "1,2,3"
Example 4:
// coercing an array of elements with different types to a primitive
var x = [undefined, null, true, 1, "5", new Date(), {a: 2}, [1,2,3]]
x.valueOf(); // -> Array itself : not a primitive, call toString now.
x.toString(); // -> ",,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3" : string is a primitive, I like it!
In the example above, when x
is coerced to a primitive, the result would be the string: ",,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3"
Coercing to number
: ToNumber(input)
The following summarizes what ToNumber
does for different input types:
-
undefined
→NaN
-
null
→0
-
number
→ return the number itself -
boolean
→0
if input isfalse
,1
if input istrue
-
string
→ parse string, if string is a number return number, otherwise returnNaN
. Empty string returns0
.
Eg: "5"
→ 5
, ""
→ 0, "29xY"
→ NaN
-
object
: callToPrimitive
first, then callToNumber
on the result ofToPrimitive
. See thetoPrimitive
section to learn how objects get coerced to primitives
Date
→ callsgetTime()
method and returns the value
Coercing to string
: ToString(input)
The following summarizes what ToNumber
does for different input types:
-
undefined
→"undefined"
-
null
→"null"
-
number
→ “number” -
boolean
→ “true”, “false” -
string
→ return the string itself. -
object
: callToPrimitive
first with a hint ofstring
, then callToString
on the result ofToPrimitive
. See thetoPrimitive
section to learn how objects get coerced to primitives
Date
→ callstoString()
method and returns the value
Coercing to boolean
: ToBoolean(input)
In JavaScript, the following values are falsy:
undefined
null
0
""
NaN
Everything else is truthy. Knowing this fact will make it easy to know what happens after a value is coerced to a boolean:
-
undefined
→false
-
null
→false
-
number
→false
if 0, otherwise,true
-
boolean
→ input iteself -
string
→false
if empty string""
, otherwisetrue
-
object
→true
. This follows thatarray
→true
-
Date
→true
Double Equals Examples
Now that we know how double equals work, we can have some fun and look at some examples. I am going to explain what path JavaScript take to decide what to output.
Example: [] == 0
- Are types different? Yes, so we have to take the long path:
- Are we comparing
null
withundefined
? No, go to next check: - Are we comparing a
number
with astring
? No, go to next check: - Are we comparing a
boolean
with something else? No, go to the next check: - Are we comparing an
object
with anumber
orstring
, orsymbol
? YES! we are comparing the array object with a number. Ok, let’s coerce the array to a primitive and call double equals again:
- calling
valueOf
on the array: output is[]
, which is not a primitive and I can’t use it. Let’s calltoString
: - calling
toString
on the array: output is""
. Perfect, it is a primitive string, and I can use it.
Calling double equals again with the result of coercing the object to a primitive:
"" == 0
And now we go back to the beginning and start over again:
- Are the types the same? no, take the long path:
- b. Are we comparing
null
withundefined
? no, go to next check: - Are we comparing a
number
with astring
? YES, now let’s coerce the string to a number. coercing the empty string to a number will return0
. Call double equals again and start over:
0 == 0
- Are the types the same? Yes, call triple equals
0 === 0
and the result is true
So in summary:
[] == 0
↓
"" == 0
↓
0 == 0
↓
0 === 0
→ true
Let’s keep this example in mind and look at other examples. In the following examples, I will let you to go through the whole algorithm.
Example: [] == "0"
[] == "0"
↓
"" == "0"
↓
"" === "0"
→ false
Example: false == 1
false == 1
↓
0 == 1
↓
0 === 1
→ false
Example: {} == false
{} == false
↓
{} == 0
↓
"[object Object]" == 0
↓
NaN == 0
↓
NaN === 0
→ false
Example: undefined == false
undefined == false
↓
undefined == 0
→ false
Example: [1,2,3] == 123
:
[1,2,3] == 123
↓
"1,2,3" == 123
↓
NaN == 123
↓
NaN === 123
↓
→ false
Example: [123] == 123
:
[123] == 123
↓
"123" == 123
↓
123 == 123
↓
123 === 123
↓
→ true
Example: [123] == "123"
:
[123] == "123"
↓
"123" == "123"
↓
"123" === "123"
↓
→ true
For the following examples, x
is defined as the following:
var x = {
a: '...',
toString: function () {
return false;
},
valueOf: function () {
return new Boolean(true);
}
};
Example: x == "0"
:
x == "0"
↓
false == "0"
↓
0 == "0"
↓
0 == 0
↓
0 === 0
↓
→ true
Example: x == 5
:
x == 5
↓
false == 5
↓
0 == 5
↓
0 === 5
→ false
Example: x == [1,2,3]
:
x == [1,2,3] // both are objects
↓
x === [1,2,3] // they are stored at different addresses
→ false // the address is not the same
###Conclusion
So, which one should you choose? I’ll let you decide on that :)
I hope the == vs === JavaScript examples in this tutorial will guide you to code better.
###Other tutorials you might be interested in