in Notes

Note 2. ECMAScript. Equality operators.

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:

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

Write a Comment

Comment

27 Comments

  1. I used to be one of these developers, who compared with === everywhere. Now I am not – thanks, man!

    I see in code examples of many ECMAScript programmers (which before used === everywhere) the usage of == with typeof and similar checks.

    Can you show examples of these similar checks?

    Thanks again,
    Ron

  2. @Ron Marco

    Thanks.

    Can you show examples of these similar checks?

    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:

    if (["a", "b", "c"].indexOf("a") == 0) {
      ...
    }

    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:

    if (arg === false) {
      ...
    }

    However, if you need to consider all falsy values, then == false can be used. But personally I prefer to use ! in such case:

    if (!arg) {
      ...
    }

    So, it can be any safe-case in which you are completely sure.

    Dmitry.

  3. Hi Dmitry,

    Good writeup 🙂

    Minor typo:

    null == false, // false 
    undefined == false, // false

    You probably meant the === here 🙂

  4. @qFox

    Hi Peter, thanks,

    You probably meant the === here

    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.

  5. 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!

  6. 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?

  7. @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.

  8. 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.

  9. 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’.

  10. 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?

  11. @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. with typeof.

  12. There is no excuse for ==, it is just a waste of time.

  13. "" + a == "" + b // comparing strings
    
    // the same, but more human-read versions
    
    String(a) == String(b)
    

    Interestingly these two lines are not equivalent. In the first case a.valueOf() is called, in the second case – a.toString().

    var obj1 = {
      toString: function() {return ""},
      valueOf: function() {return 1}
    };
    
    ""+obj1 == true; // true
    String(obj1) == false; // also true
  14. @johnjbarton

    There is no excuse for ==, it is just a waste of time.

    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

    Interestingly these two lines are not equivalent. In the first case a.valueOf() is called, in the second case – a.toString().

    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 + calls valueOf first (and then, if it returns not a primitive, tries toString) for the variable value; while String applied as a function calls toString directly. I’ll add this note.

  15. 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. 🙂

  16. Dmitry,

    Why it is so?

    "a" == 1; //false
    "a" == 0; //false
    "a" == NaN; //false
    NaN == NaN; //false
    

    I believe ToNumber("a") should be equal to NaN. On the other hand typeof NaN is "number", so eventually it should compare NaN == NaN. Why it leads to false?

  17. @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 use isNaN function.

  18. Notice, that undefined and null are not equal to false in non-strict == comparison — again because of ToNumber

    But:

    Number(null); // => 0
    null == 0; // => false
    0 == null; // => false
    

    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?

  19. Another way to check for NaN is

    NaN != NaN
  20. However, if you need to consider all falsy values, then == false can be used.

    That’s incorrect, but can you explain why?

    if ("-1" == false || undefined == false || null == false) {
      console.log('never reached');
    }
    
  21. @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 to Number(false), which is 0. And undefined, and null are an exception, and has a special case in the == algorithm.

  22. @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.

    String(Symbol()); // "Symbol()"
    Symbol() + ''; // TypeError
    `${Symbol()}`; // TypeError
    

    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.

    const foo = {
      toString() {
        return 'abc';
      },
      valueOf() {
        return 123;
      },
    };
    String(foo); // "abc"
    foo + ''; // "123"
    `${foo}`; // "abc"
    

    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.

  23. @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.