Go to main content
December 19, 2020
Cover image

Javascript has a lot of types of equality. From my point of view, it is important to know how they work and the differences between them to understand when to use each one. Let’s start with the most known: the strict equality.

The strict equality or triple equals === checks the the equality between two values with their types. When we have objects ( or []), a function or a Symbol, the references are compared:

console.log(23 === 23); // true
console.log('hello' === 'hello'); // true
console.log(0 === -0); // true
console.log(undefined === undefined); // true
console.log(null === null); // true
console.log(null === undefined); // false
console.log('23' === 23); // false
console.log({} === {}); // false
console.log([] === []); // false
console.log(NaN === NaN); // false
console.log(Infinity === Infinity); // true
console.log(Infinity === -Infinity); // false
console.log(Symbol('aSymbol') === Symbol('aSymbol')); // false
console.log(
  Symbol.for('aSymbol') === Symbol.for('aSymbol'),
); // true

const firstMethod = () => {};
const secondMethod = () => {};

console.log(firstMethod === secondMethod); // false

The weak equality or double equals == converts the two values to a same type and then compare them. When we have objects ( or []), a function or a Symbol, the references are compared:

console.log(23 == 23); // true
console.log('bonjour' == 'bonjour'); // true
console.log(0 == -0); // true
console.log(undefined == undefined); // true
console.log(null == null); // true
console.log(null == undefined); // true
console.log('23' == 23); // true
console.log({} == {}); // false
console.log([] == []); // false
console.log(NaN == NaN); // false
console.log(Infinity == Infinity); // true
console.log(Infinity == -Infinity); // false
console.log(Symbol('unSymbole') == Symbol('unSymbole')); // false
console.log(
  Symbol.for('unSymbole') == Symbol.for('unSymbole'),
); // true

const firstMethod = () => {};
const secondMethod = () => {};

console.log(firstMethod == secondMethod); // false

Here are some specific cases:

console.log('' == false); // true
console.log('' == 0); // true
console.log([] == ''); // true
console.log([[]] == ''); // true
console.log([1] == true); // true
console.log([0] == false); // true
console.log([[0]] == false); // true

Object.is works like the strict equality except for two cases:

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

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

The shallow equal is a comparison based on Object.is which will also compare first level values of object ( or []):

// Implementation of Object.is not to use polyfill
function is(x, y) {
  // Let's detect if we are not in the case 0 === -0 where we should have !Object.is(0, -0)
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // The second case is NaN !== NaN but Object.is(NaN, NaN)
    return x !== x && y !== y;
  }
}

function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true;

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  // Let's go through all keys of objects
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

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

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(
        objB,
        keysA[i],
      ) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

In this case we have the following equalities:

console.log(shallowEqual(0, -0)); // false
console.log(shallowEqual({}, {})); // true
console.log(shallowEqual([], [])); // true
console.log(
  shallowEqual(new String('value'), new String('value')),
); // true
console.log(
  shallowEqual(
    { firstKey: 'firstValue', secondKey: 'secondValue ' },
    { firstKey: 'firstValue', secondKey: 'secondValue ' },
  ),
); // true
console.log(
  shallowEqual(
    ['firstValue', 'secondValue'],
    ['firstValue', 'secondValue'],
  ),
); // true
console.log(
  shallowEqual({ 0: 'firstValue', 1: 'secondValue ' }, [
    'firstValue',
    'secondValue ',
  ]),
); // true

console.log(
  shallowEqual({ firstKey: {} }, { firstKey: {} }),
); // false
console.log(
  shallowEqual({ firstKey: [] }, { firstKey: [] }),
); // false
console.log(
  shallowEqual(
    { firstKey: 'firstValue' },
    { firstKey: 'firstValue', secondKey: 'secondValue' },
  ),
); // false
console.log(
  shallowEqual(
    ['firstValue'],
    ['firstValue', 'secondValue'],
  ),
); // false
console.log(shallowEqual([{}], [{}])); // false

The deep equal make the deeply comparison of objects possible (not only the first level):

// Implementation of Object.is not to use polyfill
function is(x, y) {
  // Let's detect if we are not in the case 0 === -0 where we should have !Object.is(0, -0)
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    // The second case is NaN !== NaN but Object.is(NaN, NaN)
    return x !== x && y !== y;
  }
}

function deepEqual(object1, object2) {
  if (is(object1, object2)) {
    return true;
  }

  if (
    typeof object1 !== 'object' ||
    object1 === null ||
    typeof object2 !== 'object' ||
    object2 === null
  ) {
    return false;
  }

  // Let's go through all keys of objects
  const object1Keys = Object.keys(object1);
  const object2Keys = Object.keys(object2);

  if (object1Keys.length !== object2Keys.length) {
    return false;
  }

  for (let i = 0; i < object1Keys.length; i++) {
    const key = object1Keys[i];

    if (
      !Object.prototype.hasOwnProperty.call(object2, key) ||
      // We call recursively the method to go deeper as soon as we have an object
      !deepEqual(object1[key], object2[key])
    ) {
      return false;
    }
  }

  return true;
}

In this case we have the following equalities:

console.log(deepEqual(0, -0)); // false
console.log(deepEqual({}, {})); // true
console.log(deepEqual([], [])); // true
console.log(deepEqual({ firstKey: 'firstValue', secondKey: 'secondValue '}, { firstKey: 'firstValue', secondKey: 'secondValue '})); // true
console.log(deepEqual(['firstValue', 'secondValue'], ['firstValue', 'secondValue'])); // true
console.log(deepEqual({ 0: 'firstValue', 1: 'secondValue '}, ['firstValue', 'secondValue '])); // true
console.log(deepEqual(deepEqual({ firstKey: {} }, { firstKey: {} })); // true
console.log(deepEqual({ firstKey: [] }, { firstKey: [] })); // true
console.log(deepEqual([ {} ], [ {} ])); // true

console.log(deepEqual(deepEqual({ firstKey: { deepKey: { key: 'value1' } } }, { firstKey: { deepKey: { key: 'value2' } } })); // false
console.log(deepEqual({ firstKey: 'firstValue' }, { firstKey: 'firstValue', secondKey: 'secondValue' })); // false
console.log(deepEqual([ 'firstValue' ], [ 'firstValue', 'secondValue' ])); // false

You can find me on Twitter if you want to comment this post or just contact me. Feel free to buy me a coffee if you like the content and encourage me.