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.

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
);

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.

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

);

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)

);

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 ==

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.

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.

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.

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.

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

Tags: , , , ,

 
 
 

22 Comments:

  1. Gravatar of Ron Marco Ron Marco
    25. June 2010 at 23:47

    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. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    26. June 2010 at 00:43

    @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. Gravatar of qFox qFox
    16. July 2010 at 12:31

    Hi Dmitry,

    Good writeup :)

    Minor typo:

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

    You probably meant the === here :)


  4. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    16. July 2010 at 12:57

    @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. Gravatar of qFox qFox
    16. July 2010 at 17:39

    Oh neato, cool :D


  6. Gravatar of free free
    28. April 2011 at 11:11

    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!


  7. Gravatar of John Merge John Merge
    10. September 2011 at 21:03

    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?


  8. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    10. September 2011 at 21:29

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


  9. Gravatar of John Merge John Merge
    11. September 2011 at 14:52

    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.


  10. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    11. September 2011 at 18:36

    @John Merge

    Yes, that’s it; thanks for the test, I corrected the sentence to reflect the things in more accurate way.


  11. Gravatar of John Merge John Merge
    12. September 2011 at 00:42

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


  12. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    12. September 2011 at 11:08

    @John Merge

    Right; that’s why e.g. CoffeeScript translates == directly into ===, avoiding non-strict equality at all.


  13. Gravatar of John Merge John Merge
    16. September 2011 at 11:28

    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?


  14. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    16. September 2011 at 11:56

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


  15. Gravatar of johnjbarton johnjbarton
    27. December 2011 at 04:12

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


  16. Gravatar of xx11 xx11
    31. December 2011 at 11:52

    "" + 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

  17. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    3. January 2012 at 21:53

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


  18. Gravatar of Steven Steven
    8. January 2012 at 13:06

    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. :)


  19. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    8. January 2012 at 14:22

    @Steven, great! Glad to see this material is used in education.


  20. Gravatar of Pavel Pavel
    6. September 2012 at 15:56

    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?


  21. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    7. September 2012 at 09:55

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


  22. Gravatar of keripix keripix
    12. May 2014 at 19:43

    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?


Leave a Reply

Code: For code you can use tags [js], [text], [ruby] and other.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>