Short answer: never. This post looks at five possible exemptions from the rule to always use === and explains why they aren’t.
JavaScript has two operators for determining whether two values are equal [1]:
- The strict equality operator === only considers values equal that have the same type.
- The “normal” (or lenient) equality operator == tries to convert values of different types, before comparing like strict equality.
Prefer intention-revealing code to terse code.Remember: Your code is only written once but likely read many times – make it as easy for readers as possible.
Case 1: You know what you are comparing with
For example, with the typeof operator [2], you can be sure that the result will be a string. Then == is safe to use, because we can be sure that it won’t perform any conversion shenanigans:
if (typeof x == "function") {
...
}
However, there are two reasons not to do this:
- Consistency: You don’t get any benefits from using == here, so why deviate from the simple rule of avoiding it?
- Simplicity and performance: In general, === is the simpler operation, because it doesn’t convert its operands. The performance landscape across JavaScript engines is uneven [3], but on most browsers === is as fast or faster than ==.
Case 2: comparing with undefined and null
With ==, undefined and null are in the same equivalence class – they are equal to themselves and to each other, but to nothing else (including values that are considered false in JavaScript):
> null == null
true
> undefined == null
true
> false == null
false
> 0 == null
false
Thus, the following if statement checks for either null or undefined.
if (x == null) {
...
}
However, the increased terseness is offset by such code being less explicit about your intentions: If the check for undefined matters then you might as well put that in writing. Otherwise, if a JavaScript beginner reads your code, they might think you are only checking for null. If an advanced programmer reads your code, they might think that you made an error and meant to write ===.
if (x === undefined || x === null) {
...
}
If you can afford to be a little sloppy then the above can be abbreviated to
if (!x) {
...
}
The usual caveat applies: The condition holds if x has either of the following “falsy” values.
undefined
null
false
0
""
Case 3: comparing strings and numbers
Scenario: You are working with user interface code or code that handles server-side parameters. Then you are likely to get handed numbers encoded as strings. If x is such a string, you can compare it via
if (x == 123) {
...
}
But why not tell readers of your code that if x isn’t a number, it should be converted to one?
if (Number(x) === 123) {
...
}
Case 4: comparing objects to primitives
With == you can compare a primitive to either a primitive or an instance of a wrapper type [4]:
> function isAbc(x) { return x == "abc" }
> isAbc("abc")
true
> isAbc(new String("abc"))
true
With ===, you can’t:
> new String("abc") === "abc"
false
The left-hand side is an object while the right-hand side is a primitive. Hence, they don’t have the same type and aren’t strictly equal. However, you again should make it a priority to explain to a reader of your code what your intentions are. Given the expression
x == "abc"
What do you want to accomplish?
- Is it really about comparing either a wrapped string or a string to the right-hand side? It seems unlikely, but if so, you should be very careful about documenting what is going on.
- Do you want to convert x to a string? Then you can write more explicitly
String(x) === "abc" - Do you want to extract a wrapped primitive? Then you should consider
x.valueOf() === "abc"
Case 5: JavaScript is flexible – my code should be, too
The argument goes like this: I want my code to exhibit the same flexibility that JavaScript normally does. And == helps me with that. Examples of where JavaScript is flexible, it automatically converts values:
> "abc" + false
'abcfalse'
> 3 + true
4
> +"73"
73
There are several counter-arguments to the above hypothesis:
- Even the standard conversions might not always work the way you need them too. Examples:
> !"false" false > 7 + "3" '73' > Number("") 0 - Lenient equality works differently from how conversions are normally performed:
> 2 == false false > 2 == true false > Boolean(2) true - An explicit conversion plus strict equality results in more descriptive code. Compare: Flexibility via lenient equality.
function is123Implicit(x) { return x == 123; } > is123Implicit(123) true > is123Implicit(new Number(123)) true > is123Implicit("123") trueAlternative: Flexibility via an explicit conversion and strict equals.function is123Explicit(x) { x = Number(x); return x === 123; } > is123Explicit(123) true > is123Explicit(new Number(123)) true > is123Explicit("123") true - Who says your code has to be flexible? It can be argued that JavaScript’s default flexibility is a bug rather than a feature. Writing defensive code more quickly exposes bugs. A defensive version of is123Explicit() looks as follows:
function is123Defensive(x) { if (typeof x !== "number") { throw new TypeError("Not a number: "+x); } return x === 123; }If you want to pass anything but a primitive number to this function, you must perform a conversion first.
13 comments:
I disagree. In my opinion (x == null) is most convient way to check whether value is provided.
Also normal equality operators are useful when you want to compare e.g. primitive strings to string objects, it's corner case, but at some point you may want to work with object strings.
Thanks for your comment. I’ve added case 3 to answer your objection.
What about new input types for numbers, such as range?
I haven't looked into it, but some browsers might pass this on as string, while others might pass it on as a number. If you'd want to compare this value to something, == would be safer to use.
Thanks,
String(x) is good solution and that's what's internally used in all ES5 string functions (they're by design generic).
However valueOf may fail in some cases e.g. valueOf and toString for Dates return different values, so I wouldn't take that solution.
What you compare it to determines the implicit conversion that will be made. Hence, using either String() or Number() will make things explicit, but won’t change anything else.
What's the definitive way of checking if a variable has never been declared in javascript? I always get some "xxx is not defined" in my webconsole.
Revealing intentions is definitely not to be under-appreciated by programmers. However, in JavaScript its a well established pattern to use == null to test for both.
If you need to specifically test for either, you can do that with ===. If you use === undefined || === null, in my humble opinion you're just being unnecessarily redundant.
I'd personally counter that with if your api treats `null` and `undefined` as the same your doing it wrong.
If I see `== null` I either think
- you don't understand `==` vs `===`
- you don't care about the difference between `null` or `undefined`, thus your doing it wrong
- you know exactly what your doing and want to test for both `null` and undefined` but your lazy. I'll refactor it to the more verbose check
Agreed on all points. The main issue with `==` is you can't tell whether you know what your doing or your using `==` because your an idiot.
Always favor `===`.
Losing 20 bytes in my code would just be stupid. Bandwith is money.
It’s a matter of taste and prioritization: I think it is more important to help humans than to save a little space (there are only so many comparisons in code and each time you save one character). Furthermore, a good minification tool will do all the ugly space-saving stuff for you, so even those savings aren’t there any, more.
hi,I'm confuse about your point that valueOf may fail in some cases e.g. valueOf and toString for Dates return different values..Could you tell me in what cases..Or give some link to learn..thanks in advance
IMO code density has a cost. If you are saying something should be initialized, saying "if (something)" not only is more clear, but it is less for your brain to parse then "if ((something === undefined) || (something === null))" I find the first can be read at a glance, and reads exactly what the intention is. If it is unclear whether i was testing for intiialization, or if it was not 0, then i think that speaks to the clarity of the surrounding context, and probably needs a refactoring anyways.
There are only a few places where the coercion rules can be confusing in js, 99% of the time if you take the time to learn them, you will not get in trouble. If you unit test, you probably won't ever run into an issue. I will do "if (something)" every time, and I haven't been bit by a coercion bug in years.
All that being said, the community in a general way agrees with you over me (I also only use semi-colons in the one place they matter), so YMMV, to each his own, etc, etc
Post a Comment