JavaScript Equality: A Complete Guide to Comparing Values in JS

JavaScript provides four different ways to compare two values for equality: ==, ===, Object.is, and !==. Each of these operators has different rules and behaviors, and choosing the right one can be tricky. In this article, we will explain how each operator works, how to compare different data types and values, and what are the best practices for using equality operators in JavaScript.

Loose equality (==)

The loose equality operator (==) checks whether its two operands are equal, returning a Boolean result. Unlike the strict equality operator, it attempts to convert and compare operands that are of different types. For example:

JAVASCRIPT
                        
"1" == 1; // true
0 == false; // true
null == undefined; // true

The loose equality operator follows the Abstract Equality Comparison Algorithm, which can be roughly summarized as follows:

  • If the operands have the same type, they are compared as follows:
    • Object: return true only if both operands reference the same object.
    • String: return true only if both operands have the same characters in the same order.
    • Number: return true only if both operands have the same value. +0 and -0 are treated as the same value. If either operand is NaN, return false; so, NaN is never equal to NaN.
    • Boolean: return true only if operands are both true or both false.
    • BigInt: return true only if both operands have the same value.
    • Symbol: return true only if both operands reference the same symbol.
  • If one of the operands is null or undefined, the other must also be null or undefined to return true. Otherwise return false.
  • If one of the operands is an object and the other is a primitive, convert the object to a primitive. At this step, both operands are converted to primitives (one of String, Number, Boolean, Symbol, and BigInt). The rest of the conversion is done case-by-case.
  • If they are of the same type, compare them using step 1.
  • If one of the operands is a Symbol but the other is not, return false.
  • If one of the operands is a Boolean but the other is not, convert the boolean to a number: true is converted to 1, and false is converted to 0. Then compare the two operands loosely again.
  • Number to String: convert the string to a number. Conversion failure results in NaN, which will guarantee the equality to be false.
  • Number to BigInt: compare by their numeric value. If the number is ±Infinity or NaN, return false.
  • String to BigInt: convert the string to a BigInt using the same algorithm as the BigInt() constructor. If conversion fails, return false.

Loose equality is symmetric: A == B always has identical semantics to B == A for any values of A and B (except for the order of applied conversions).

The most notable difference between this operator and the strict equality operator is that the strict equality operator does not attempt type conversion. Instead, the strict equality operator always considers operands of different types to be different.

The loose equality operator can be useful when you want to compare values that may have different types but represent the same thing, such as user input or JSON data. However, it can also lead to unexpected results and bugs, especially when dealing with numbers, booleans, null, undefined, and objects. Therefore, it is generally recommended to use the strict equality operator whenever possible, and only use the loose equality operator when you are sure that the type conversion is intended and safe.

Strict equality (===)

The strict equality operator (===) compares two values for equality. Neither value is implicitly converted to some other value before being compared. If the values have different types, the values are considered unequal. If the values have the same type, are not numbers, and have the same value, they're considered equal. Finally, if both values are numbers, they're considered equal if they're both not NaN and are the same value, or if one is +0 and one is -0. For example:

JAVASCRIPT
                        
1 === 1; // true
"hello" === "hello"; // true
1 === "1"; // false
0 === false; // false
null === undefined; // false

The strict equality operator follows the Strict Equality Comparison Algorithm, which can be simply described as follows:

  • If the operands have different types, return false.
  • If the operands have the same type, they are compared as follows:
    • Object: return true only if both operands reference the same object.
    • String: return true only if both operands have the same characters in the same order.
    • Number: return true only if both operands have the same value. +0 and -0 are treated as the same value. If either operand is NaN, return false; so, NaN is never equal to NaN.
    • Boolean: return true only if operands are both true or both false.
    • BigInt: return true only if both operands have the same value.
    • Symbol: return true only if both operands reference the same symbol.

Strict equality is symmetric: A === B always has identical semantics to B === A for any values of A and B.

Strict equality is almost always the correct comparison operation to use. For all values except numbers, it uses the obvious semantics: a value is only equal to itself. For numbers it uses slightly different semantics to gloss over two different edge cases. The first is that floating point zero is either positively or negatively signed. This is useful in representing certain mathematical solutions, but as most situations don't care about the difference between +0 and -0, strict equality treats them as the same value. The second is that floating point includes the concept of a not-a-number value, NaN, to represent the solution to certain ill-defined mathematical problems: negative infinity added to positive infinity, for example. Strict equality treats NaN as unequal to every other value — including itself.

The strict equality operator is more reliable, consistent, and predictable than the loose equality operator. It avoids the confusion and errors that can arise from implicit type conversions. It also makes the code more readable and expressive, as it clearly shows the intention of the comparison. Therefore, it is recommended to always use the strict equality operator, unless there is a specific reason to use the loose equality operator.

Object.is(value1, value2)

The Object.is method determines whether two values are the same value. It has the same behavior as the strict equality operator, except for two special cases: it treats +0 and -0 as different values, and it treats NaN as equal to itself. For example:

JAVASCRIPT
                        
Object.is(1, 1); // true
Object.is("hello", "hello"); // true
Object.is(1, "1"); // false
Object.is(0, false); // false
Object.is(null, undefined); // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

The Object.is method follows the SameValue Algorithm, which can be simply described as follows:

  • If the operands have different types, return false.
  • If the operands have the same type, they are compared as follows:
    • Object: return true only if both operands reference the same object.
    • String: return true only if both operands have the same characters in the same order.
    • Number: return true only if both operands have the same value. +0 and -0 are treated as different values. If both operands are NaN, return true; so, NaN is equal to NaN.
    • Boolean: return true only if operands are both true or both false.
    • BigInt: return true only if both operands have the same value.
    • Symbol: return true only if both operands reference the same symbol.

Object.is is symmetric: Object.is(A, B) always has identical semantics to Object.is(B, A) for any values of A and B.

The Object.is method is useful when you want to compare two values for identity, without any type conversion or special treatment for numbers. It can be used to check for the presence of NaN, or to distinguish between +0 and -0. However, these cases are rare and usually not relevant for most applications. Therefore, the Object.is method is not very commonly used, and the strict equality operator is preferred in most situations.

Inequality operators (!= and !==)

The inequality operators (!= and !==) are the logical inverses of the equality operators (== and ===). They check whether two values are not equal, returning a Boolean result. The != operator is the loose inequality operator, and it performs type conversion before comparing the operands. The !== operator is the strict inequality operator, and it does not perform type conversion before comparing the operands. For example:

JAVASCRIPT
                        
1 != 1; // false
"hello" != "hello"; // false
1 != "1"; // false
0 != false; // false
null != undefined; // false
1 !== 1; // false
"hello" !== "hello"; // false
1 !== "1"; // true
0 !== false; // true
null !== undefined; // true

The inequality operators follow the same algorithms as the equality operators, but with the opposite result. The != operator follows the Abstract Equality Comparison Algorithm, and the !== operator follows the Strict Equality Comparison Algorithm.

The inequality operators are often used to check for the absence or presence of a certain value or condition. For example, to check if a variable is null or undefined, you can use != null or !== undefined. To check if a variable is truthy or falsy, you can use != false or !== true. However, as with the equality operators, the inequality operators can also produce unexpected results and bugs, especially when dealing with different types and values. Therefore, it is generally recommended to use the strict inequality operator whenever possible, and only use the loose inequality operator when you are sure that the type conversion is intended and safe.

How to compare different data types and values

As we have seen, JavaScript provides different ways to compare two values for equality or inequality, depending on whether you want to perform type conversion or not. However, sometimes you may need to compare values that are not of the same type, and you may not want to rely on the implicit type conversion of the loose equality operator. In such cases, you can use some of the following techniques to compare different data types and values in JavaScript:

To compare a string and a number, you can use the Number() function to convert the string to a number, and then use the strict equality operator. For example:

JAVASCRIPT
                        
Number("1") === 1; // true
Number("hello") === NaN; // false

To compare a boolean and a number, you can use the Number() function to convert the boolean to a number, and then use the strict equality operator. For example:

JAVASCRIPT
                        
Number(true) === 1; // true
Number(false) === 0; // true

To compare a boolean and a string, you can use the Boolean() function to convert the string to a boolean, and then use the strict equality operator. For example:

JAVASCRIPT
                        
Boolean("1") === true; // true
Boolean("") === false; // true

To compare an object and a primitive, you can use the valueOf() or toString() methods to convert the object to a primitive, and then use the strict equality operator. For example:

JAVASCRIPT
                        
({ value: 1 }).valueOf() === 1; // true
({ value: "hello" }).toString() === "[object Object]"; // true

To compare two objects, you can use the Object.is method to check for identity, or you can use a custom function to compare the properties and values of the objects. For example:

JAVASCRIPT
                        
Object.is({ value: 1 }, { value: 1 }); // false
function deepEqual(obj1, obj2) {
  // check if both are null or undefined
  if (obj1 == null && obj2 == null) {
    return true;
  }
  // check if both are objects
  if (typeof obj1 == "object" && typeof obj2 == "object") {
    // check if both have the same number of properties
    if (Object.keys(obj1).length == Object.keys(obj2).length) {
      // check if each property and value are equal
      for (let key in obj1) {
        if (!deepEqual(obj1[key], obj2[key])) {
          return false;
        }
      }
      return true;
    }
  }
  // otherwise, use strict equality
  return obj1 === obj2;
}
deepEqual({ value: 1 }, { value: 1 }); // true

Conclusion

In this article, we have learned how to understand and use the different equality operators in JavaScript, such as ==, ===, Object.is, !=, and !==. We have also learned how to compare different data types and values, and what are the best practices for using equality operators in JavaScript. Here are some key takeaways:

  • The loose equality operator (==) performs type conversion before comparing the operands. It can be useful when you want to compare values that may have different types but represent the same thing, but it can also lead to unexpected results and bugs, especially when dealing with numbers, booleans, null, undefined, and objects.
  • The strict equality operator (===) does not perform type conversion before comparing the operands. It is more reliable, consistent, and predictable than the loose equality operator. It avoids the confusion and errors that can arise from implicit type conversions. It also makes the code more readable and expressive, as it clearly shows the intention of the comparison. Therefore, it is recommended to always use the strict equality operator, unless there is a specific reason to use the loose equality operator.
  • The Object.is method determines whether two values are the same value. It has the same behavior as the strict equality operator, except for two special cases: it treats +0 and -0 as different values, and it treats NaN as equal to itself. It can be used to check for the presence of NaN, or to distinguish between +0 and -0. However, these cases are rare and usually not relevant for most applications. Therefore, the Object.is method is not very commonly used, and the strict equality operator is preferred in most situations.
  • The inequality operators (!= and !==) are the logical inverses of the equality operators (== and ===). They check whether two values are not equal, returning a Boolean result. The != operator is the loose inequality operator, and it performs type conversion before comparing the operands. The !== operator is the strict inequality operator, and it does not perform type conversion before comparing the operands. The inequality operators are often used to check for the absence or presence of a certain value or condition, but they can also produce unexpected results and bugs, especially when dealing with different types and values. Therefore, it is generally recommended to use the strict inequality operator whenever possible, and only use the loose inequality operator when you are sure that the type conversion is intended and safe.
  • To compare different data types and values, you can use some of the following techniques: use the Number() function to convert a string or a boolean to a number, use the Boolean() function to convert a string to a boolean, use the valueOf() or toString() methods to convert an object to a primitive, use the Object.is method to check for identity, or use a custom function to compare the properties and values of two objects.