Skip to content

JavaScript Equality Comparison: ==, ===, and Object.is()

In daily life, we have different standards for judging whether two things are the same. For example, identical twins may look "the same" but are "similar" at the DNA level. JavaScript's equality comparison has similar concepts: sometimes we only care if values are equal (==), sometimes we require both value and type to be the same (===), and sometimes we need more strict judgment (Object.is()).

Overview of Three Comparison Methods

JavaScript provides three equality comparison methods:

javascript
// 1. Equality operator == (loose equality)
console.log(5 == "5"); // true (allows type conversion)

// 2. Strict equality operator === (strict equality)
console.log(5 === "5"); // false (no type conversion)

// 3. Object.is() (most strict)
console.log(Object.is(5, 5)); // true
console.log(Object.is(NaN, NaN)); // true (different from ===)

Equality Operator == (Abstract Equality)

The equality operator == performs type conversion when comparing values, attempting to make both sides' types consistent before comparison.

Basic Rules

javascript
// Same type, direct comparison
console.log(5 == 5); // true
console.log("hello" == "hello"); // true
console.log(true == true); // true

// Different types are converted
console.log(5 == "5"); // true (string converted to number)
console.log(0 == false); // true (boolean converted to number)
console.log(1 == true); // true
console.log("" == false); // true (both convert to 0)

// Special rules for null and undefined
console.log(null == undefined); // true (special rule)
console.log(null == 0); // false
console.log(undefined == 0); // false

Type Conversion Rules

When using == to compare values of different types, JavaScript follows these conversion rules:

javascript
// Rule 1: String vs Number comparison, string converts to number
console.log("42" == 42); // true ("42" converts to 42)
console.log("0" == 0); // true
console.log("" == 0); // true (empty string converts to 0)

// Rule 2: Boolean converts to number (true → 1, false → 0)
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true == "1"); // true (true → 1, "1" → 1)
console.log(false == ""); // true (false → 0, "" → 0)

// Rule 3: Object vs primitive comparison, object converts to primitive
let obj = { valueOf: () => 42 };
console.log(obj == 42); // true (calls valueOf())

let arr = [1, 2];
console.log(arr == "1,2"); // true (calls toString())

// Rule 4: null and undefined are only equal to themselves and each other
console.log(null == undefined); // true
console.log(null == null); // true
console.log(undefined == undefined); // true
console.log(null == 0); // false
console.log(null == ""); // false
console.log(undefined == 0); // false

== Pitfalls

javascript
// Pitfall 1: Empty string and 0
console.log("" == 0); // true
console.log("0" == 0); // true
console.log("" == "0"); // false (both strings, no conversion)

// Pitfall 2: Empty arrays
console.log([] == 0); // true ([] → "" → 0)
console.log([] == ""); // true ([] → "")
console.log([] == false); // true

// Pitfall 3: Object comparison
console.log({} == {}); // false (different objects, different references)
console.log([] == []); // false (different arrays, different references)

// Pitfall 4: Strange results
console.log([] == ![]); // true
// Explanation: ![] → false, [] == false → true

console.log("" == []); // true
console.log("" == [null]); // true
console.log(0 == []); // true

// Pitfall 5: true not equal to "true"
console.log(true == "true"); // false
// true → 1, "true" → NaN, 1 != NaN

== Practical Applications

Although == has many pitfalls, it's convenient in certain scenarios:

javascript
// Check for null or undefined
function processValue(value) {
  if (value == null) {
    // Matches both null and undefined
    return "No value";
  }
  return value;
}

console.log(processValue(null)); // "No value"
console.log(processValue(undefined)); // "No value"
console.log(processValue(0)); // 0
console.log(processValue("")); // ""

// Equivalent to more verbose writing:
if (value === null || value === undefined) {
  // ...
}

Strict Equality Operator === (Strict Equality)

The strict equality operator === doesn't perform type conversion; it requires both type and value to be the same to return true.

Basic Rules

javascript
// Same type and value
console.log(5 === 5); // true
console.log("hello" === "hello"); // true
console.log(true === true); // true

// Different types directly return false
console.log(5 === "5"); // false
console.log(0 === false); // false
console.log(1 === true); // false
console.log("" === 0); // false

// null and undefined are not equal
console.log(null === undefined); // false
console.log(null === null); // true
console.log(undefined === undefined); // true

=== Advantages

javascript
// Predictable behavior
console.log(0 === false); // false (clear and unambiguous)
console.log("" === 0); // false
console.log([] === 0); // false

// Avoid type conversion pitfalls
let userInput = "0";

if (userInput == false) {
  // true (might not be what you want)
  console.log("This runs!");
}

if (userInput === false) {
  // false (matches expectation)
  console.log("This won't run");
}

// Safer comparison
function isValidNumber(value) {
  return typeof value === "number" && !isNaN(value);
}

console.log(isValidNumber(42)); // true
console.log(isValidNumber("42")); // false
console.log(isValidNumber(NaN)); // false

=== Special Cases

javascript
// NaN is not equal to itself
console.log(NaN === NaN); // false

// Positive zero and negative zero are equal
console.log(0 === -0); // true
console.log(+0 === -0); // true

// Object comparison still compares references
console.log({} === {}); // false
console.log([] === []); // false

let obj1 = { name: "Alice" };
let obj2 = obj1;
console.log(obj1 === obj2); // true (same reference)

Object.is() (Same Value Algorithm)

Object.is() is an ES6 method that provides the most strict equality judgment.

Basic Usage

javascript
// Same as === in most cases
console.log(Object.is(5, 5)); // true
console.log(Object.is("hello", "hello")); // true
console.log(Object.is(true, true)); // true

console.log(Object.is(5, "5")); // false
console.log(Object.is(null, undefined)); // false

// But different in two special cases:

// 1. NaN equals NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true ✓

// 2. +0 and -0 are not equal
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false ✓

Object.is() Application Scenarios

javascript
// Scenario 1: Check for NaN
function isActualNaN(value) {
  return Object.is(value, NaN);
}

console.log(isActualNaN(NaN)); // true
console.log(isActualNaN("hello")); // false
console.log(isActualNaN(undefined)); // false

// Or use Number.isNaN()
console.log(Number.isNaN(NaN)); // true

// Scenario 2: Distinguish +0 and -0
function isNegativeZero(value) {
  return Object.is(value, -0);
}

console.log(isNegativeZero(-0)); // true
console.log(isNegativeZero(0)); // false
console.log(isNegativeZero(+0)); // false

// Scenario 3: Precise comparison (avoid special value issues)
function strictEqual(a, b) {
  return Object.is(a, b);
}

Three-Way Comparison Summary

Comparison Table

javascript
// Compare NaN
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true ✓

// Compare +0 and -0
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false ✓

// Compare null and undefined
console.log(null == undefined); // true ✓
console.log(null === undefined); // false
console.log(Object.is(null, undefined)); // false

// Type conversion
console.log(5 == "5"); // true ✓
console.log(5 === "5"); // false
console.log(Object.is(5, "5")); // false

// Normal value comparison
console.log(5 == 5); // true
console.log(5 === 5); // true
console.log(Object.is(5, 5)); // true

Selection Guide

javascript
// 1. Use === by default (99% of cases)
if (value === expectedValue) {
  // Safest choice
}

// 2. Use == only when checking null/undefined
if (value == null) {
  // Check for null or undefined
  // This is the only recommended scenario for using ==
}

// 3. Use Object.is() when you need to distinguish NaN or ±0
if (Object.is(result, NaN)) {
  // Check if it's NaN
}

if (Object.is(value, -0)) {
  // Check if it's negative zero
}

Array and Object Comparison

Reference Comparison

javascript
// Array and object comparison compares references, not content
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
let arr3 = arr1;

console.log(arr1 == arr2); // false (different references)
console.log(arr1 === arr2); // false
console.log(arr1 == arr3); // true (same reference)
console.log(arr1 === arr3); // true

let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
let obj3 = obj1;

console.log(obj1 == obj2); // false (different references)
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true (same reference)

Deep Comparison

If you need to compare object or array contents, you need to implement it yourself or use utility libraries:

javascript
// Simple deep comparison (handles basic cases only)
function deepEqual(a, b) {
  // Same reference
  if (a === b) return true;

  // Different types
  if (typeof a !== typeof b) return false;

  // null check
  if (a === null || b === null) return false;

  // Non-object types
  if (typeof a !== "object") return a === b;

  // Arrays
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) return false;
    }
    return true;
  }

  // Objects
  let keysA = Object.keys(a);
  let keysB = Object.keys(b);

  if (keysA.length !== keysB.length) return false;

  for (let key of keysA) {
    if (!keysB.includes(key)) return false;
    if (!deepEqual(a[key], b[key])) return false;
  }

  return true;
}

// Tests
console.log(deepEqual([1, 2, 3], [1, 2, 3])); // true
console.log(deepEqual({ a: 1 }, { a: 1 })); // true
console.log(deepEqual({ a: 1 }, { a: 2 })); // false

// In real projects, use mature libraries
// Such as Lodash's _.isEqual()
// import { isEqual } from 'lodash';
// console.log(isEqual([1, 2], [1, 2]));  // true

Common Errors and Best Practices

Error 1: Mixing == and ===

javascript
// ❌ Inconsistent
if (value == null) {
  // Using ==
  // ...
} else if (value === 0) {
  // Using ===
  // ...
}

// ✅ Consistent use of === (except checking null/undefined)
if (value === null || value === undefined) {
  // ...
} else if (value === 0) {
  // ...
}

// Or consistently use == null
if (value == null) {
  // ...
} else if (value === 0) {
  // ...
}

Error 2: Comparing with == to Boolean Values

javascript
let hasValue = "true";

// ❌ Wrong
if (hasValue == true) {
  // false ("true" converts to NaN)
  console.log("This won't run");
}

// ✅ Correct: use truthiness
if (hasValue) {
  // true (non-empty string is truthy)
  console.log("This runs");
}

// ✅ Correct: explicit comparison
if (hasValue === "true") {
  console.log("Explicit check");
}

Error 3: Comparing NaN

javascript
let result = 0 / 0; // NaN

// ❌ Wrong
if (result == NaN) {
  // Always false
  console.log("This won't work");
}

if (result === NaN) {
  // Always false
  console.log("This won't work either");
}

// ✅ Correct
if (isNaN(result)) {
  // Use isNaN()
  console.log("This works");
}

if (Number.isNaN(result)) {
  // More strict, recommended
  console.log("This is better");
}

if (Object.is(result, NaN)) {
  // Also works
  console.log("This also works");
}

Best Practices

javascript
// 1. Use === by default
let score = 100;
if (score === 100) {
  console.log("Perfect!");
}

// 2. Use == when checking null/undefined
function processData(data) {
  if (data == null) {
    // Check both null and undefined
    return [];
  }
  return data;
}

// 3. Avoid direct boolean comparison, use truthiness/falsiness
let username = "Alice";

// ❌ Not good
if (username === true) {
  // Always false
  // ...
}

// ✅ Good
if (username) {
  // Check if truthy
  console.log(`Hello, ${username}`);
}

// 4. Be careful with object comparison
let user1 = { name: "Bob" };
let user2 = { name: "Bob" };

// ❌ Can't compare content this way
if (user1 === user2) {
  // false
  // ...
}

// ✅ Compare specific properties
if (user1.name === user2.name) {
  console.log("Same name");
}

// ✅ Or use deep comparison function
if (deepEqual(user1, user2)) {
  console.log("Same content");
}

// 5. Use ESLint to enforce ===
// .eslintrc.js
// {
//   rules: {
//     'eqeqeq': 'error'  // Enforce ===
//   }
// }

Special Value Comparisons

undefined vs null

javascript
// Difference between the two
let a; // undefined (unassigned)
let b = null; // null (explicitly assigned as empty)

console.log(a == b); // true (special rule)
console.log(a === b); // false (different types)

// Check methods
console.log(a === undefined); // true
console.log(b === null); // true

// Check both
function isEmpty(value) {
  return value == null; // null or undefined
}

console.log(isEmpty(null)); // true
console.log(isEmpty(undefined)); // true
console.log(isEmpty(0)); // false
console.log(isEmpty("")); // false

0, -0, and false

javascript
// 0 and false
console.log(0 == false); // true
console.log(0 === false); // false

// +0 and -0
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false

// Practical application of distinguishing positive/negative zero
function signedZero(value) {
  if (value === 0) {
    return 1 / value === Infinity ? "+0" : "-0";
  }
  return value;
}

console.log(signedZero(+0)); // "+0"
console.log(signedZero(-0)); // "-0"

Summary

Understanding the details of equality comparison helps you write more reliable code with fewer bugs.

Key Points Recap:

  • == (Equality): Allows type conversion, complex rules, error-prone
  • === (Strict Equality): No type conversion, predictable behavior, recommended
  • Object.is(): Most strict comparison, correctly handles NaN and ±0
  • Use === by default, avoid type conversion pitfalls
  • Only use == when checking null/undefined
  • NaN is not equal to any value (including itself), use Number.isNaN() to check
  • Objects and arrays compare references, not content
  • Use ESLint's eqeqeq rule to enforce ===
  • For deep object comparison, use specialized comparison functions or libraries
  • Understand truthiness/falsiness, avoid unnecessary boolean comparisons