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

That’s because of 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, start to apply ToNumber conversion consequently to each operand, until you have the same types — numbers, and therefore — the correct result.

Let’s start from the simple example related with 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 I mentioned we don’t have ToBoolean conversion, but still ToNumber. And I remind, 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 I 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 — here 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)

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 ideology “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, is faster in many current implementations than === operator. With different types, obviously === is faster.

But of course, the correct functioning of a system is more essential than a few “milliseconds”. Therefore, repeat, if you feel that you forgot what some operator or function returns and need to consider the type, use the === operator. Objectively, I 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 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.

Dmitry.


Tags: , , , ,

 
 
 

5 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


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>