Implementing Deep Equality: A Step-by-Step JavaScript Guide
📚 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
obj1andobj2are primitive values (numbers, strings, booleans, undefined, null) and are strictly equal, they are considered deeply equal. - Same Object Reference: If
obj1andobj2refer 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 ifobj1is not an object or if it isnull. Remember thattypeof nullreturns'object', soobj1 === nullis necessary to correctly identifynull.typeof obj2 !== 'object' || obj2 === null: Does the same check forobj2.- If either
obj1orobj2is 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 returnsfalse.
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 currentkeyfromobj1does not exist inobj2. 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 thedeepEqualfunction itself to compare the values associated with the currentkeyin both objects. This handles nested objects, arrays, and any other complex data types. If this recursive call returnsfalse(meaning the values for this key are not deeply equal), then the entire comparison fails, and the function returnsfalse.
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
.jsfile (e.g.,deepEqual.js) and run it usingnode 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)
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.