In this small note we clarify some technical features related with equality operators.
As we know in ECMAScript equality can be non-transitive.
Non-transitive equality
That means if:
A == B B == C
it’s not guaranteed in some cases that
A == C
As an example:
console.log( '0' == 0, // true 0 == '', // true '0' == '' // false );
Or another example:
var a = new String("foo"); var b = "foo"; var c = new String("foo"); console.log( a == b, // true b == c, // true a == c // false );
Strict ===
and non-strict ==
equality
The results of the examples above are caused by the implicit type conversion applied with using of non-strict equal ==
operator. As a result, there were recommendations to avoid ==
completely and use always ===
, i.e. strict-equal. Additionally, non-strict equal was declared an “evil part” of ECMAScript.
Of course, the main objective (and this applies to all languages) is improved abstraction in order to easily understand the language constructions and decrease ambiguities. Programmers need not remember much about simple comparison operator to avoid ambiguities.
The most confusing cases are related with falsy values, when comparing with a boolean value on the right hand side. In such cases we don’t even have ToBoolean
implicit conversion. And to clarify this question, let’s consider the general cases of implicit type conversion, related with the non-strict equal.
Values null
and undefined
are always equal to each other in such case:
console.log( null == undefined, // true undefined == null // true );
If one of the operands is a number then at the end (after some intermediate type conversions) the second operand will also be always converted to a number.
ToNumber
as the main coercition of ==
Actually, ToNumber
is the main conversion used in case of non-strict ==
operator. Remember it in order to be able determinate the result of the implicit conversion easily. If types are different, just start to apply ToNumber
conversion sequentially to each operand, until you have the same types — numbers, and therefore — the correct result.
Let’s start from the simple example related with the ToNumber
conversion:
console.log( 1 == "1", // true -> ToNumber("1") -> 1 == 1 "1" == 1, // the same, ToNumber("1") but for the first operand );
For object values in such case, first ToPrimitive
conversion is applied, after which again ToNumber
is used (but only if ToPrimitive
did not return a string and on the left hand side we also have a string — then of course there is no ToNumber
conversion). In turn ToPrimitive
is depended on internal [[DefaultValue]]
method, which provides the result of either toString
or valueOf
methods.
console.log( 1 == {}, // false, default valueOf/toString 1 == ({valueOf: function () {return 1;}}), // true 1 == ({toString: function () {return "1";}}), // true 1 == [1] // true, with default valueOf/toString );
Not ToBoolean
but still ToNumber
Regarding boolean as it was mentioned we don’t have ToBoolean
conversion, but still ToNumber
. And remember, that for a boolean argument ToNumber
results 1
if the argument is true
, and 0
if the argument is false
.
console.log( 1 == true, // true, ToNumber(true) -> 1 == 1 0 == false, // true, ToNumber(false) -> 0 == 0 // more interesting examples "001" == true, // true, ToNumber("001") == ToNumber(true) -> 1 == 1 "002" == true, // false, by the same conversion chain -> 2 != 1 "0x0" == false, // true, the same (for hex-value and boolean) -> 0 == 0 "" == false, // true, " \t\r\n " == false // true );
In such cases we recommend to use strict equal ===
operator or explicit ToBoolean
conversion:
console.log( !"0", // false !"0x0", // false "0" === false // false // etc. );
Notice, that undefined
and null
are not equal to false
in non-strict ==
comparison — again because of ToNumber
, but not ToBoolean
conversion (of course they are also not equal to false
with strict ===
comparison). However, this case is special in 11.9.3 algorithm — there ToNumber
(in step 19) is applied only to the second operand (false
in this case on the right hand side), and then we get the step 22 in the algorithm where false
for such comparison is returned:
console.log( null == false, // false, because null != ToNumber(false) undefined == false, // false, because undefined != ToNumber(false) );
Boolean false
object is true
Remember also some subtle cases, as e.g. with Boolean
object and false
value. Notice, that explicit ToBoolean
conversion — with applying Boolean
as a function or !
operator won’t help because for objects ToBoolean
always returns true
:
var bool = new Boolean(false); var data = !bool ? "false" : "true"; // ToBoolean won't help console.log(data); // "true" data = bool === false ? "false" : "true"; console.log(data); // "true" console.log( Boolean(bool), // true !!bool // true );
In such case non-strict ==
equal can help to catch this case — because of ToNumber
conversion:
data = bool == false ? "false" : "true"; console.log(data); // "false"
Also a direct call to the valueOf
can help with this case:
console.log(bool.valueOf() === false); // OK, false, as with ==
Manual type coercion
Additionally, you may use manual type conversion to compare exactly e.g. strings, numbers or booleans:
"" + a == "" + b // comparing strings +a == +b // comparing numbers !a == !b // comparing booleans // the same, but more human-read versions String(a) == String(b) Number(a) == Number(b) Boolean(a) == Boolean(b)
Notice though, that +
operator calls valueOf
method to convert its operand to a primitive value, while String
applied as a function calls directly toString
:
var foo = { toString: function () { return "toString"; }, valueOf: function () { return "valueOf"; } }; console.log("" + foo); // "valueOf" console.log(String(foo)); // "toString"
However since we adjoin the result with the (empty) string "" + ...
, at the end we get the string primitive value in any case.
Always explicit with ===
Another and already mentioned approach is to avoid ==
at all just as ===
would be the only one equality operator in the language. For that you will need to do all type conversions manually. For example:
if (a === 1 || a.toString() === "1" || a === true) { ... }
Without avoiding it can be shorter:
if (a == 1) { ... }
The approach “explicit is better than implicit” is good and convenient for many programmers, so it can be accepted and used for programming on ECMAScript.
Safe cases of ==
Notice, nevertheless, that there are also many cases when strict equal ===
is non-necessary and doesn’t bring any additional robustness to the code. These cases are completely safe and related to the standard behavior and algorithms of the ECMA-262 specification. One of such widespread useless comparisons is:
if (typeof foo === "string") { ... }
The main thing which you should know that algorithms of ==
and ===
operator are completely equivalent, word-for-word, if types of operands are the same. There was even a similar quiz question before. All interested can clarify this question, checking the corresponding sections of the both ES3 and ES5 specifications. These are: 11.9.3 The Abstract Equality Comparison Algorithm and 11.9.6 The Strict Equality Comparison Algorithm.
Summary
So what’s the general recommendation of using ==
and ===
operators, if you decided not avoiding ==
operator?
Still the same: in cases when you are not sure of what is returned by some custom function or what value a comparing variable has, and at the same time you need to consider the type or identity of an object, use strict ===
equal operator. In all other cases, including completely safe cases such as with typeof
operator comparing its result with a string, non-strict ==
operator is enough.
Today there are some libraries which use strict equal with typeof
and similar constructs that are completely safe for ==
checks. At the same time, there are also libraries which do not do this.
Nit-picking on micro-optimization, non-strict ==
operator comparing with the same types can be faster in some implementations than ===
operator. However, in many current implementation the difference either is too small to consider, or there is no difference at all. Besides, in this case ===
can even be faster in some implementations. With different types, obviously ===
is faster.
But of course, the correct functioning of a system is more essential than a few “milliseconds”. Therefore, as we said, if you feel that you forgot what some operator or function returns and need to consider the type, use the ===
operator. Objectively, we can assume that a programmer can forget it in some cases, including completely safe cases such as typeof
. Then a programmer might just have chosen ===
as an only operand exactly to not be bothered with ==
subtle issues and to concentrate on other more important things.
Even if is known that typeof
operator always results a string, it’s not a crime to use checks such as typeof foo === "string"
— they can be used e.g. for consistency. I’ve even heard once a reason, that === can be used e.g. for a self-documenting of the typeof
operator. I.e. mentioning explicitly that typeof
results a string. But, here can be the other confusing effect — just like typeof
can results something else (values of different types) besides a string. But it is not so, and it always returns a string, so ==
is enough in such case. And in order to be able use the language in full force, it is good to know how ECMAScript works and which results return their operators and standard functions.
Further reading
As the additional literature, you can consider these sections of ECMA-262 (both: 3rd and 5th editions), including already mentioned algorithms of equality operators:
- 9. Type Conversion;
- 11.9.3 The Abstract Equality Comparison Algorithm;
- 11.9.6 The Strict Equality Comparison Algorithm.
If you have any questions or additions I’ll be glad to discuss them in comments.
Written by: Dmitry Soshnikov
Published on: 2010-06-25
I used to be one of these developers, who compared with === everywhere. Now I am not – thanks, man!
Can you show examples of these similar checks?
Thanks again,
Ron
@Ron Marco
Thanks.
Any safe-check in which result you are completely sure. E.g. all built-in functions and operators which behavior is defined by the specification.
E.g. you know that indexOf always returns a number. So, comparing it with another number, == is enough:
In general, using of strict equal should be there when it’s needed. If you are expecting a boolean false value, then compare it with exactly boolean false value:
However, if you need to consider all falsy values, then == false can be used. But personally I prefer to use ! in such case:
So, it can be any safe-case in which you are completely sure.
Dmitry.
Hi Dmitry,
Good writeup 🙂
Minor typo:
You probably meant the === here 🙂
@qFox
Hi Peter, thanks,
Nope, it’s not a typo. I meant exactly non-strict == equality — to show that ToNumber, but not ToBoolean conversion is used. However, this case is special — ToNumber by 11.9.3 algorithm is applied only to the boolean operand (not to undefined or null), so I mentioned this explicitly.
Dmitry.
Oh neato, cool 😀
according to ecma262-3 8.6.2.6 [[DefaultValue]](hint)
http://bclary.com/2004/11/07/#a-8.6.2.6
now i want to get the [[DefaultValue]] of []
so according to ecma,like this:
When the [[DefaultValue]] method of O is called with hint Number, the following steps are taken:
1. Call the [[Get]] method of object O with argument “valueOf”.
[].valeOf() => []//itself
2. If Result(1) is not an object, go to step 5.
[] is an object
3. Call the [[Call]] method of Result(1), with O as the this value and an empty argument list.
Result(1) => [],[] don’t implement [[Call]]
4. If Result(3) is a primitive value, return Result(3).
so ,no Result(3),or it is still []
5. Call the [[Get]] method of object O with argument “toString”.
[].toString => “”
6. If Result(5) is not an object, go to step 9.
Result(5) => “” is not an object, go to step 9
7. Call the [[Call]] method of Result(5), with O as the this value and an empty argument list.
8. If Result(7) is a primitive value, return Result(7).
9. Throw a TypeError exception.
error? god!
Dmitry,
About:
“Nit-picking on micro-optimization, non-strict == operator, comparing with the same types, is faster in many current implementations than === operator. With different types, obviously === is faster.”
Have you made some benchmarks which confirm that?
@John Merge
Yes, there are some on jspref. However, currently it’s can be not so and really depends on implementation. Now I see that in FF6 both
==
and===
are nearly the same by time (with the same types of course) and===
is much faster than==
on different types. Sometimes===
is faster than==
on the same types. So in nowadays I wouldn’t care about it and this my sentence from this note is not so essential.According to this test:
http://jsperf.com/test-typeof-with-and
We can see that:
1. In IE 7 and 8 there is no difference.
2. In Firefox 6.02 ‘==’ is faster than ‘===’.
3. In Firefox 3.6.13 there is no difference.
4. In Opera 11.51 ‘===’ is faster than ‘==’.
5. In Chrome 12.0.742 ‘===’ is faster than ‘==’.
6. In Chrome 13.0.782 there is no difference.
@John Merge
Yes, that’s it; thanks for the test, I corrected the sentence to reflect the things in more accurate way.
That’s cool. I also completely agree with this sentence:
“But of course, the correct functioning of a system is more essential than a few “milliseconds”.
Typing ‘===’ in case of typeof and ‘string’ type usually means that developer did not understand very well JS and he/she wrote it is this way ‘just to be sure’.
@John Merge
Right; that’s why e.g. CoffeeScript translates
==
directly into===
, avoiding non-strict equality at all.Hmm, but… is it correct? I mean, I’ve seen ‘ == ‘ to be used _intentionally_ by some developers.
Couldn’t that conversion to lead to errors?
@John Merge
It’s correct for Coffee’s semantics. That is, there manual type casting is required. For JS semantics, I use myself often just
==
, e.g. withtypeof
.There is no excuse for ==, it is just a waste of time.
Interestingly these two lines are not equivalent. In the first case
a.valueOf()
is called, in the second case –a.toString()
.@johnjbarton
If to leave only one strict operator, then to replace semantics of
==
with the===
. But it’s hard because of backward compatibilities — we can’t just leave only one equality operator now.It’s only possible with invention of a new language — this is how CoffeeScript exactly does — it compiles
==
into the===
.I agree that it’s good to use only one style in order not to think about all the cases. But with e.g. the same
typeof
I don’t see any hazard using==
comparing with a string.@xx11
Yes, it’s true in your case since you compare different types on left and right hand sides.
But nevertheless, the end result after applying
+
operator, is the string (according to 11.6.1, step 7), so we compare strings having"" + ...
on both sides.However, yes, it’s a good note that
+
callsvalueOf
first (and then, if it returns not a primitive, triestoString
) for the variable value; whileString
applied as a function callstoString
directly. I’ll add this note.Hey Dmitry, you’re a genius man . nice work you got here.
I am an IT student and I need this for my class man.
Thanks for this great work. 🙂
@Steven, great! Glad to see this material is used in education.
Dmitry,
Why it is so?
I believe
ToNumber("a")
should be equal toNaN
. On the other handtypeof NaN
is"number"
, so eventually it should compareNaN == NaN
. Why it leads tofalse
?@Pavel, correct, however NaN is the only value that isn’t equal to anything, even to itself. To check whether a value is
NaN
once can useisNaN
function.But:
After reading the spesification, I think the above are false because when a differnt type is being compared with null, undefined, and NaN, the return value is false.
I’m not an expert on this. So maybe my reasoning is incorrect? Or maybe I understood you incorrectly?
Another way to check for NaN is
That’s incorrect, but can you explain why?
@Igor,
Agreed, the sentence is not correct, will need to update/remove. As to your example,
ToNumber
coercion is used, so you can trace it by applying it on different value. E.g.Number("-1")
is-1
is not equal toNumber(false)
, which is0
. Andundefined
, andnull
are an exception, and has a special case in the==
algorithm.@Dmitry,
I come back to read your post again. What a informative post. Thanks.
There are some minor differences between `String(foo)`, `foo + “”`, and `${foo}`:
1. the former one will handle value of Symbol type separately, the latter 2 will throw TypeError exception.
2. Binary + operator is a little different on value of Object type, because it will apply `ToPrimitive` abstract operation on left operand and right operand, then it will apply `ToString` abstract operation, the 2 others will apply `ToString` directly.
So I think `String(foo)`, `foo + “”`, `${foo}` are not equivalent to each other. However, `Boolean(foo)` is equivalent to `!!foo` and `Number(foo)` is equivalent to `+foo`.
Thanks again for you lead me to dive into ECMAScript spec.
By the way, anyone has confusion about equality operators, I highly recommand you guys spend some time and patient on `abstract equality comparision` and `strict equality comparsion` in ES spec. And you will find what Dmitry says is not hard to understand.
@Xiao – thanks for the great feedback. Yes, there are certain differences, especially when it comes to the Symbol as you mention. This article is dated before the Symbol type was introduced, but that’s a great addition you mention. Probably will need to add these extra details.