How to Get a Perfect Deep Equal in JavaScript
Tags: javascript, webdev, programming, frontend
Published: 2024-05-27 07:16:58


In JavaScript, we can use `==` , `===` operator and `Object.is` method to judge the equality of two variable values. But if we want to compare two variable values deeply, can they meet our needs?

* * *

# ==

The `==` operator is a loose equality operator. When comparing two different types of values, it will first try to convert them to the same type, and then compare them. The specific algorithm used is [Abstract Equality Comparison Algorithm](https://www.ecma-international.org/ecma-262/5.1/?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript#sec-11.9.3), its rules are complex and difficult to remember, you can also check its short description [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript#description), and you can also experience its conversion process interactively through this [link](https://felix-kling.de/js-loose-comparison/#[1%2C2]==%221%2C2%22).

It is arguably the least rigorous equality operator, and many results may surprise you.

# ===

The `===` operator is a strict equality operator. It always considers operands of different types to be different. The specific algorithm used is [Strict Equality Comparison Algorithm](https://www.ecma-international.org/ecma-262/5.1/?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript#sec-11.9.6), its rules are easier to remember, you can also check its short description [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript#description).

It is our most used equality operator. It looks strict, but it has flaws in the following cases:

- Numbers must have the same numeric values. `+0` and `-0` are considered to be the same value.

- If either operand is `NaN`, it will return false.

- If both operands are objects, it will only judge whether their reference addresses are the same.

# Object.is

The `Object.is` method behaves the same as the `===` operator in most cases, but has the opposite result of the `===` operator in the following two cases:

- `+0` and `-0` are considered different values and will return false.

- `NaN` and `NaN` are considered to be the same value and will return true.

It doesn’t mean that `Object.is`is stricter than `===` operator. We should deal with the corresponding usage requirements according to the specific characteristics of `Object.is` .

Note that `Object.is` behaves the same as the `===` operator if the two operands are objects, or only determines whether their reference addresses are the same.

* * *

# How to get a Deep Equal

For the case where the operands are all objects, we expect Deep Equal to give the answer we want. For example, for any non-primitive objects `x` and `y` which have the same structure but are distinct objects themselves, we would expect Deep Equal to return true.

I explained the characteristics of JavaScript data types in the [_How to Get a Perfect Deep Copy in JavaScript_](https://webdeveloper.beehiiv.com/p/get-perfect-deep-copy-javascript?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript) article published earlier. It is precise because of these characteristics that a perfect Deep Equal needs to consider many edge cases, and its performance is destined to be poor. , so in React, we do not use Deep Equal to judge whether the state has changed before and after, but Shallow Equal.

So let’s take a look at how Shallow Equal in React is implemented?

# Shallow Equal in React

I don’t change the original logic here, just remove the compatibility code to improve readability. Its original file is [here](https://github.com/facebook/react/blob/main/packages/shared/shallowEqual.js?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript), you can check it out for comparison.

```javascript
function shallowEqual(objA, objB) {
  // P1
  if (Object.is(objA, objB)) {
    return true;
  }

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

  // P3
  var keysA = Object.keys(objA);
  var keysB = Object.keys(objB);

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

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

  return true;
}
```

Note my comments in the code that start with `P` , I’ll explain in units of it:

**P1:** The first-level comparison is performed through `Object.is`, and if it is equal, it returns true, which has the effect of first-level filtering.

**P2:** Make sure both are objects and return false if either is not.

**P3:** At this point both are objects, but their reference addresses are not the same. So the next step is to loop through the key array of one of the objects, determine whether the key is its own property (as opposed to inheriting it) of another object, and determine whether the value corresponding to the key of these two objects can pass `Object.is` , that is, here React does not choose recursive judgment for performance, which means that only one layer is compared.

After reading the Shallow Equal in React, I believe you have some ideas. Let’s realize a perfect Deep Equal based on it.

# Get the perfect Deep Equal based on Shallow Equal

{% embed https://stackblitz.com/edit/js-zl2f8a?file=index.js %}

The test code here uses the deep copy function implemented in the previous article. Of course, you can also modify and test the results directly on StackBlitz, for example, you can remove the comments at the end of the code to see the change in the result.

```javascript
const deepEqual = (objA, objB, map = new WeakMap()) => {
  // P1
  if (Object.is(objA, objB)) return true;

  // P2
  if (objA instanceof Date && objB instanceof Date) {
    return objA.getTime() === objB.getTime();
  }
  if (objA instanceof RegExp && objB instanceof RegExp) {
    return objA.toString() === objB.toString();
  }

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

  // P4
  if (map.get(objA) === objB) return true;
  map.set(objA, objB);

  // P5
  const keysA = Reflect.ownKeys(objA);
  const keysB = Reflect.ownKeys(objB);

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

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Reflect.has(objB, keysA[i]) ||
      !deepEqual(objA[keysA[i]], objB[keysA[i]], map)
    ) {
      return false;
    }
  }

  return true;
};
```

**P1:** Like Shadow Equal, use `Object.is` for first-level filtering.

**P2:** Special handling is required for `Date` and `RegExp`, so use `Date.prototype.getTime()` for Date to get the timestamp and compare, and `RegExp.prototype.toString()` for RegExp to get string and compare.

**P3:** Like Shadow Equal, make sure both are objects and return false if either is not.

**P4:** Use WeakMap as a hash table to solve the circular reference problem. If the two have been compared before, it will return true, which means that it will not affect the final result.

**P5:** Compared to Shadow Equal, we upgrade to `Reflect.ownKeys` to get all keys. Then we also judge the length of the attribute array, and then loop through all the attribute keys of `objA`, and use `Reflect.has` to judge whether there are the same attributes on `objB`. Finally, we upgrade `Object.is` to recursive processing, and constantly judge whether the deep values are equal.

If you look at the [deep copy functions](https://webdeveloper.beehiiv.com/p/get-perfect-deep-copy-javascript?utm_source=webdeveloper.beehiiv.com&utm_medium=newsletter&utm_campaign=how-to-get-a-perfect-deep-equal-in-javascript) implemented earlier, do you feel similarities between them? Yes, they are very similar in some logical judgments. You can learn by comparison. I believe it can deepen your understanding of JavaScript data structure.

* * *

*If you find this helpful, [**please consider subscribing**](https://webdeveloper.beehiiv.com/) to my newsletter for more insights on web development. Thank you for reading!*