Implementing Deep Equality: A Step-by-Step JavaScript Guide

4 min read

📚 Quick Review: This practical application is built upon a fundamental programming concept. Review the Theory Lesson here first.


Bringing Theory to Life: A Practical Implementation of Deep Equality

Understanding the concept of deep equality is one thing, but implementing it effectively in JavaScript is another. This practical guide will walk you through a common and efficient recursive implementation of a deepEqual function, breaking down each line of code to reveal its purpose and contribution to the overall logic. By the end, you’ll not only understand how it works but also how to apply it in your projects.

The Deep Equal Function: Code Breakdown

Let’s examine the provided JavaScript function designed to perform a deep comparison between two values.

function deepEqual(obj1, obj2) {  if (obj1 === obj2) return true;  if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;  const keys1 = Object.keys(obj1);  const keys2 = Object.keys(obj2);  if (keys1.length !== keys2.length) return false;  for (let key of keys1) {    if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;  }  return true;}

Line-by-Line Explanation

if (obj1 === obj2) return true;

This is the first and most crucial base case. It handles several scenarios:

  • Primitive Equality: If both obj1 and obj2 are primitive values (numbers, strings, booleans, undefined, null) and are strictly equal, they are considered deeply equal.
  • Same Object Reference: If obj1 and obj2 refer to the exact same object in memory, they are also considered deeply equal without needing further traversal. This is an important optimization.

if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;

This line performs essential type and null checks:

  • typeof obj1 !== 'object' || obj1 === null: Checks if obj1 is not an object or if it is null. Remember that typeof null returns 'object', so obj1 === null is necessary to correctly identify null.
  • typeof obj2 !== 'object' || obj2 === null: Does the same check for obj2.
  • If either obj1 or obj2 is not a non-null object (e.g., one is a number and the other is an object, or one is null and the other is an object), they cannot be deeply equal, so the function returns false.

const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

Here, we extract all enumerable property names (keys) of both objects into arrays using Object.keys(). This function only returns string-keyed properties, not Symbol-keyed ones, and only enumerable properties.

if (keys1.length !== keys2.length) return false;

A quick and efficient check: if the two objects have a different number of properties, they cannot be deeply equal. This avoids unnecessary deeper comparisons.

for (let key of keys1) { ... }

This loop iterates over each key found in the first object (obj1). For each key, we perform two critical checks:

if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;

Inside the loop, this condition ensures both structural and value equality for each property:

  • !keys2.includes(key): Checks if the current key from obj1 does not exist in obj2. If a key is missing in the second object, they are not deeply equal.
  • !deepEqual(obj1[key], obj2[key]): This is the heart of the recursion. It calls the deepEqual function itself to compare the values associated with the current key in both objects. This handles nested objects, arrays, and any other complex data types. If this recursive call returns false (meaning the values for this key are not deeply equal), then the entire comparison fails, and the function returns false.

return true;

If the function reaches this point, it means all initial checks passed, the number of keys matched, and every property (including nested ones) was found to be deeply equal. Therefore, the two objects are deeply equal.

Execution Environment and Examples

This deepEqual function is standard JavaScript and can be executed in any JavaScript environment:

  • Browser Console: Open your browser’s developer tools (F12), navigate to the Console tab, paste the function, and then call it.
  • Node.js: Save the function in a .js file (e.g., deepEqual.js) and run it using node deepEqual.js, or use it within a larger Node.js application.

Example Usage:

const objA = { a: 1, b: { c: 2 } };const objB = { a: 1, b: { c: 2 } };const objC = { a: 1, b: { c: 3 } };const objD = { a: 1, b: { c: 2 }, d: 4 };const arr1 = [1, { x: 10 }];const arr2 = [1, { x: 10 }];const arr3 = [1, { x: 20 }];console.log(deepEqual(objA, objB)); // trueconsole.log(deepEqual(objA, objC)); // falseconsole.log(deepEqual(objA, objD)); // false (different number of keys)console.log(deepEqual(arr1, arr2)); // true (arrays are treated as objects with numeric keys)console.log(deepEqual(arr1, arr3)); // falseconsole.log(deepEqual(null, null)); // trueconsole.log(deepEqual(1, 1));     // trueconsole.log(deepEqual(1, '1'));   // falseconsole.log(deepEqual({ a: undefined }, {})); // false (Object.keys doesn't include undefined values, but the key 'a' exists)
💡 Developer Tip: This implementation of deepEqual works well for plain JavaScript objects and arrays. However, it does not handle special object types like Date objects, RegExp objects, Map, Set, or objects with circular references. For production-grade applications, consider using a well-tested library like Lodash’s _.isEqual(), which accounts for these edge cases and often includes performance optimizations.

Conclusion

The deepEqual function provides a powerful mechanism for comparing the true content of complex JavaScript objects. By understanding its recursive nature and the specific checks performed at each step, you can confidently implement and debug logic that relies on structural equality, leading to more robust and predictable applications.

Leave a Reply

Your email address will not be published. Required fields are marked *