Comparing two objects in JavaScript and identifying their differences can be challenging but is essential for various programming tasks; COMPARE.EDU.VN provides a comprehensive guide to tackle this issue effectively. By exploring multiple methods, including deep comparisons and handling edge cases, you’ll gain the expertise to pinpoint the distinctions between objects precisely, streamlining your coding process. Enhance your coding precision and decision-making with detailed object analysis on compare.edu.vn, ensuring smarter, more informed comparisons.
1. Understanding Object Comparison in JavaScript
Object comparison in JavaScript presents a unique challenge. Unlike primitive data types such as numbers or strings, which can be directly compared using operators like ===
or ==
, objects require a more nuanced approach. This is because, in JavaScript, objects are compared by reference rather than by value. This section delves into the intricacies of object comparison in JavaScript, highlighting the differences between comparing primitive types and objects, and explaining the concept of object references.
1.1. Primitive vs. Object Comparison
When comparing primitive data types in JavaScript, the comparison is straightforward. For example:
let num1 = 5;
let num2 = 5;
console.log(num1 === num2); // true
let str1 = "hello";
let str2 = "hello";
console.log(str1 === str2); // true
In these cases, the values of the variables are directly compared. If the values are the same, the comparison returns true
; otherwise, it returns false
.
However, when it comes to objects, the behavior is different:
let obj1 = { name: "John", age: 30 };
let obj2 = { name: "John", age: 30 };
console.log(obj1 === obj2); // false
Even though obj1
and obj2
have the same properties and values, the comparison returns false
. This is because obj1
and obj2
are stored in different memory locations, and the ===
operator checks if they refer to the same memory location.
1.2. The Concept of Object References
In JavaScript, an object is a reference type, which means that when you create an object, you are actually creating a reference to a location in memory where the object’s data is stored. When you assign an object to a variable, you are storing the reference to that memory location in the variable.
Consider the following example:
let obj1 = { name: "John", age: 30 };
let obj2 = obj1;
console.log(obj1 === obj2); // true
obj2.age = 35;
console.log(obj1.age); // 35
In this case, obj2
is assigned the value of obj1
, which means that obj2
now holds the same reference to the memory location as obj1
. Therefore, obj1 === obj2
returns true
. When you modify obj2
, you are actually modifying the object in the memory location that both obj1
and obj2
refer to. As a result, obj1.age
also changes to 35.
1.3. Why Reference Comparison Matters
Understanding reference comparison is crucial when working with objects in JavaScript. It explains why two objects with identical properties and values are not considered equal when compared using the ===
operator. It also highlights the importance of creating deep copies of objects when you want to modify an object without affecting the original.
Consider a scenario where you want to compare two objects to see if they have the same properties and values. Using the ===
operator would not work, as it would only compare the references. Instead, you would need to iterate over the properties of both objects and compare their values individually. This is where more sophisticated object comparison techniques come into play, as discussed in the following sections.
2. Common Pitfalls in Object Comparison
When comparing objects in JavaScript, several common pitfalls can lead to unexpected results if not carefully considered. These pitfalls arise from the way JavaScript handles object comparisons and the nuances of object structures. This section will explore these common pitfalls, providing insights and strategies to avoid them.
2.1. Direct Comparison with ===
As previously discussed, using the ===
operator to compare objects directly compares their references, not their values. This can lead to incorrect results when you want to determine if two objects have the same properties and values.
let obj1 = { name: "John", age: 30 };
let obj2 = { name: "John", age: 30 };
console.log(obj1 === obj2); // false
In this case, even though obj1
and obj2
have the same properties and values, the comparison returns false
because they are stored in different memory locations.
To avoid this pitfall, you should use a more sophisticated object comparison technique that compares the properties and values of the objects, rather than their references.
2.2. Ignoring Property Order
Some object comparison techniques, such as using JSON.stringify
, are sensitive to the order of properties in the objects. This means that if two objects have the same properties and values but in a different order, they will be considered different.
let obj1 = { name: "John", age: 30 };
let obj2 = { age: 30, name: "John" };
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
In this case, even though obj1
and obj2
have the same properties and values, the comparison returns false
because the order of properties is different.
To avoid this pitfall, you should use an object comparison technique that is not sensitive to the order of properties. One approach is to sort the properties of both objects before comparing them.
2.3. Failing to Handle Nested Objects
When comparing objects with nested objects, it’s crucial to perform a deep comparison to ensure that the nested objects are also compared for equality. A shallow comparison, which only compares the top-level properties, may not detect differences in the nested objects.
let obj1 = { name: "John", address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", address: { city: "Los Angeles", country: "USA" } };
// Shallow comparison
console.log(obj1.address === obj2.address); // false
// Deep comparison (requires a custom function)
function deepCompare(obj1, obj2) {
// Implementation for deep comparison
}
console.log(deepCompare(obj1, obj2)); // false
In this case, a shallow comparison would only compare the references of obj1.address
and obj2.address
, which are different. To detect the difference in the city
property, a deep comparison is required.
2.4. Ignoring Data Types
JavaScript is a loosely typed language, which means that variables can change their data types during runtime. This can lead to unexpected results when comparing objects, especially when dealing with different data types.
let obj1 = { age: 30 };
let obj2 = { age: "30" };
console.log(obj1.age === obj2.age); // false
In this case, even though the values of obj1.age
and obj2.age
are the same, the comparison returns false
because they are of different data types (number and string, respectively).
To avoid this pitfall, you should ensure that the data types of the properties being compared are the same. You can use the typeof
operator to check the data types and perform type conversions if necessary.
2.5. Overlooking Special Values
When comparing objects, it’s important to handle special values such as null
, undefined
, and NaN
correctly. These values can behave differently in comparisons, and overlooking them can lead to incorrect results.
let obj1 = { value: null };
let obj2 = { value: undefined };
console.log(obj1.value === obj2.value); // false
let obj3 = { value: NaN };
let obj4 = { value: NaN };
console.log(obj3.value === obj4.value); // false
In the case of null
and undefined
, they are not strictly equal to each other. In the case of NaN
, it is not equal to itself.
To handle these special values correctly, you should explicitly check for them in your object comparison logic.
By being aware of these common pitfalls and implementing strategies to avoid them, you can ensure that your object comparisons are accurate and reliable.
3. Simple Object Comparison Techniques
While comparing objects in JavaScript can be complex, several simple techniques can be used for basic comparison scenarios. These techniques are easy to implement and understand, making them suitable for simple use cases. This section will explore two common simple object comparison techniques: using JSON.stringify
and manual property comparison.
3.1. Using JSON.stringify
The JSON.stringify
method is a quick and easy way to compare objects in JavaScript. It converts an object into a JSON string, which can then be compared using the ===
operator.
let obj1 = { name: "John", age: 30 };
let obj2 = { name: "John", age: 30 };
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true
In this case, JSON.stringify(obj1)
and JSON.stringify(obj2)
both return the string {"name":"John","age":30}
, which are then compared using the ===
operator, resulting in true
.
3.1.1. Advantages of JSON.stringify
- Simplicity: It is easy to implement and understand.
- Conciseness: It requires minimal code.
- Built-in: It is a built-in JavaScript method, so no external libraries are needed.
3.1.2. Limitations of JSON.stringify
- Property Order: It is sensitive to the order of properties in the objects. If two objects have the same properties and values but in a different order, they will be considered different.
- Data Types: It does not preserve data types. For example, numbers and strings with the same value will be considered equal.
- Nested Objects: It can handle nested objects, but the comparison is still based on the string representation of the objects.
- Functions and Symbols: It does not handle functions or symbols. These properties will be ignored when converting the object to a JSON string.
- Circular References: It cannot handle circular references. If an object has a circular reference,
JSON.stringify
will throw an error.
3.1.3. When to Use JSON.stringify
JSON.stringify
is suitable for simple object comparison scenarios where the order of properties, data types, and special values are not important. It is also useful for comparing objects that do not contain functions, symbols, or circular references.
3.2. Manual Property Comparison
Manual property comparison involves iterating over the properties of both objects and comparing their values individually. This technique provides more control over the comparison process and can be customized to handle specific scenarios.
function compareObjects(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
let obj1 = { name: "John", age: 30 };
let obj2 = { name: "John", age: 30 };
console.log(compareObjects(obj1, obj2)); // true
In this case, the compareObjects
function first checks if the objects have the same number of properties. If not, it returns false
. Then, it iterates over the properties of the first object and compares their values with the corresponding properties in the second object. If any of the values are different, it returns false
. Otherwise, it returns true
.
3.2.1. Advantages of Manual Property Comparison
- Control: It provides more control over the comparison process.
- Customization: It can be customized to handle specific scenarios, such as different data types or special values.
- Property Order: It is not sensitive to the order of properties in the objects.
3.2.2. Limitations of Manual Property Comparison
- Complexity: It can be more complex to implement than
JSON.stringify
. - Verbosity: It requires more code.
- Nested Objects: It does not handle nested objects by default. To compare nested objects, you would need to recursively call the
compareObjects
function.
3.2.3. When to Use Manual Property Comparison
Manual property comparison is suitable for object comparison scenarios where you need more control over the comparison process or when you need to handle specific scenarios, such as different data types or special values. It is also useful when you want to compare objects with nested objects, but you need to implement the recursive comparison logic yourself.
4. Advanced Object Comparison Techniques
For more complex object comparison scenarios, advanced techniques are required to handle nested objects, different data types, and special values. These techniques involve implementing deep comparison algorithms and customizing the comparison logic to suit specific needs. This section will explore two common advanced object comparison techniques: deep comparison and using external libraries.
4.1. Deep Comparison
Deep comparison involves recursively comparing the properties of nested objects to ensure that all levels of the object structure are compared for equality. This technique is essential when dealing with objects that contain nested objects, arrays, or other complex data structures.
function deepCompare(obj1, obj2) {
if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
return obj1 === obj2;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || !deepCompare(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
let obj1 = { name: "John", address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", address: { city: "New York", country: "USA" } };
console.log(deepCompare(obj1, obj2)); // true
let obj3 = { name: "John", address: { city: "New York", country: "USA" } };
let obj4 = { name: "John", address: { city: "Los Angeles", country: "USA" } };
console.log(deepCompare(obj3, obj4)); // false
In this case, the deepCompare
function first checks if the objects are primitive types or null. If so, it compares them directly using the ===
operator. If not, it checks if the objects have the same number of properties. If not, it returns false
. Then, it iterates over the properties of the first object and recursively calls the deepCompare
function to compare the corresponding properties in the second object. If any of the properties are different, it returns false
. Otherwise, it returns true
.
4.1.1. Advantages of Deep Comparison
- Comprehensive: It compares all levels of the object structure.
- Accurate: It provides more accurate results than shallow comparison techniques.
- Handles Complex Data Structures: It can handle nested objects, arrays, and other complex data structures.
4.1.2. Limitations of Deep Comparison
- Complexity: It can be more complex to implement than simple comparison techniques.
- Performance: It can be slower than simple comparison techniques, especially for large objects with deep nesting.
- Circular References: It can lead to infinite recursion if the objects have circular references.
4.1.3. Handling Circular References in Deep Comparison
Circular references occur when an object references itself, either directly or indirectly. This can cause infinite recursion in deep comparison algorithms. To handle circular references, you can use a set to keep track of the objects that have already been visited.
function deepCompare(obj1, obj2, visited = new Set()) {
if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
return obj1 === obj2;
}
if (visited.has(obj1) || visited.has(obj2)) {
return true; // Assume circular references are equal
}
visited.add(obj1);
visited.add(obj2);
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || !deepCompare(obj1[key], obj2[key], visited)) {
return false;
}
}
return true;
}
let obj1 = { name: "John" };
obj1.self = obj1; // Circular reference
let obj2 = { name: "John" };
obj2.self = obj2; // Circular reference
console.log(deepCompare(obj1, obj2)); // true
In this case, the deepCompare
function takes an optional visited
parameter, which is a set that keeps track of the objects that have already been visited. Before comparing two objects, the function checks if either of them is already in the visited
set. If so, it assumes that the objects are equal and returns true
. Otherwise, it adds the objects to the visited
set and continues with the comparison.
4.2. Using External Libraries
Several external libraries provide advanced object comparison capabilities, including deep comparison, handling of circular references, and customization options. These libraries can simplify the object comparison process and provide more robust and efficient solutions.
4.2.1. Lodash’s isEqual
Method
Lodash is a popular JavaScript utility library that provides a wide range of functions for working with objects, arrays, and other data structures. One of its most useful functions for object comparison is the isEqual
method.
const _ = require('lodash');
let obj1 = { name: "John", address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", address: { city: "New York", country: "USA" } };
console.log(_.isEqual(obj1, obj2)); // true
let obj3 = { name: "John", address: { city: "New York", country: "USA" } };
let obj4 = { name: "John", address: { city: "Los Angeles", country: "USA" } };
console.log(_.isEqual(obj3, obj4)); // false
The isEqual
method performs a deep comparison of two objects, handling nested objects, arrays, and other complex data structures. It also handles circular references and provides customization options.
4.2.2. Advantages of Using Lodash’s isEqual
- Simplicity: It is easy to use and requires minimal code.
- Comprehensive: It performs a deep comparison of two objects.
- Handles Circular References: It handles circular references automatically.
- Customization: It provides customization options for specific comparison scenarios.
- Performance: It is optimized for performance.
4.2.3. Other Object Comparison Libraries
Besides Lodash, several other JavaScript libraries provide object comparison capabilities, including:
- Deep-equal: A small and fast deep comparison library.
- Fast-deep-equal: A very fast deep comparison library.
- Rambda: A functional programming library that includes an
equals
function for deep comparison.
When choosing an object comparison library, consider factors such as:
- Features: Does the library provide the features you need, such as deep comparison, handling of circular references, and customization options?
- Performance: Is the library optimized for performance?
- Size: Is the library small and lightweight?
- Dependencies: Does the library have any dependencies?
- Maintenance: Is the library actively maintained?
By using advanced object comparison techniques and external libraries, you can handle complex object comparison scenarios more efficiently and accurately.
5. Identifying Differences Between Objects
In addition to comparing objects for equality, it is often necessary to identify the specific differences between two objects. This can be useful for tracking changes, updating data, or debugging issues. This section will explore techniques for identifying differences between objects, including creating a diff object and using external libraries.
5.1. Creating a Diff Object
A diff object is an object that contains the differences between two objects. It typically includes information about the properties that have been added, removed, or modified.
function getObjectDiff(obj1, obj2) {
const diff = {};
for (let key in obj1) {
if (obj1.hasOwnProperty(key)) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
diff[key] = {
oldValue: obj1[key],
newValue: obj2[key],
};
}
}
}
for (let key in obj2) {
if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) {
diff[key] = {
oldValue: undefined,
newValue: obj2[key],
};
}
}
return diff;
}
let obj1 = { name: "John", age: 30, address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", age: 35, address: { city: "Los Angeles", country: "USA" }, phone: "123-456-7890" };
let diff = getObjectDiff(obj1, obj2);
console.log(diff);
/*
{
age: { oldValue: 30, newValue: 35 },
address: { oldValue: { city: "New York", country: "USA" }, newValue: { city: "Los Angeles", country: "USA" } },
phone: { oldValue: undefined, newValue: "123-456-7890" }
}
*/
In this case, the getObjectDiff
function iterates over the properties of both objects and compares their values. If a property is present in both objects but has a different value, or if a property is present in one object but not the other, it adds an entry to the diff
object with information about the old and new values.
5.1.1. Advantages of Creating a Diff Object
- Detailed Information: It provides detailed information about the differences between two objects.
- Customizable: It can be customized to include specific information or handle specific scenarios.
- Flexible: It can be used for tracking changes, updating data, or debugging issues.
5.1.2. Limitations of Creating a Diff Object
- Complexity: It can be more complex to implement than simple comparison techniques.
- Performance: It can be slower than simple comparison techniques, especially for large objects with deep nesting.
- Nested Objects: It requires a deep comparison to identify differences in nested objects.
5.1.3. Handling Nested Objects in Diff Object Creation
To handle nested objects in diff object creation, you can recursively call the getObjectDiff
function to compare the nested objects.
function getObjectDiff(obj1, obj2) {
const diff = {};
for (let key in obj1) {
if (obj1.hasOwnProperty(key)) {
if (!obj2.hasOwnProperty(key)) {
diff[key] = {
oldValue: obj1[key],
newValue: undefined,
};
} else if (typeof obj1[key] === "object" && obj1[key] !== null && typeof obj2[key] === "object" && obj2[key] !== null) {
const nestedDiff = getObjectDiff(obj1[key], obj2[key]);
if (Object.keys(nestedDiff).length > 0) {
diff[key] = nestedDiff;
}
} else if (obj1[key] !== obj2[key]) {
diff[key] = {
oldValue: obj1[key],
newValue: obj2[key],
};
}
}
}
for (let key in obj2) {
if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) {
diff[key] = {
oldValue: undefined,
newValue: obj2[key],
};
}
}
return diff;
}
let obj1 = { name: "John", age: 30, address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", age: 30, address: { city: "Los Angeles", country: "USA" }, phone: "123-456-7890" };
let diff = getObjectDiff(obj1, obj2);
console.log(diff);
/*
{
address: { city: { oldValue: "New York", newValue: "Los Angeles" } },
phone: { oldValue: undefined, newValue: "123-456-7890" }
}
*/
In this case, the getObjectDiff
function recursively calls itself to compare nested objects. If a property is a nested object and the nested objects have differences, the diff
object will contain a nested diff
object for that property.
5.2. Using External Libraries for Diffing
Several external libraries provide advanced diffing capabilities, including deep diffing, handling of circular references, and customization options. These libraries can simplify the diffing process and provide more robust and efficient solutions.
5.2.1. Lodash’s difference
Method
Lodash’s difference
method can be used to find the differences between two arrays of objects. This method returns a new array containing the elements that are present in the first array but not in the second array.
const _ = require('lodash');
let arr1 = [{ name: "John", age: 30 }, { name: "Jane", age: 25 }];
let arr2 = [{ name: "John", age: 30 }, { name: "Mike", age: 40 }];
let diff = _.difference(arr1, arr2);
console.log(diff); // [{ name: "Jane", age: 25 }]
In this case, the difference
method compares the objects in arr1
and arr2
and returns a new array containing the objects that are present in arr1
but not in arr2
.
5.2.2. Deep-object-diff Library
The deep-object-diff
library provides a function called diff
that can be used to find the differences between two objects. This function returns a new object containing the properties that have been added, removed, or modified.
const diff = require('deep-object-diff').diff;
let obj1 = { name: "John", age: 30, address: { city: "New York", country: "USA" } };
let obj2 = { name: "John", age: 35, address: { city: "Los Angeles", country: "USA" }, phone: "123-456-7890" };
let differences = diff(obj1, obj2);
console.log(differences);
/*
{
age: 35,
address: { city: 'Los Angeles', country: 'USA' },
phone: '123-456-7890'
}
*/
In this case, the diff
function compares the objects obj1
and obj2
and returns a new object containing the properties that have been added or modified in obj2
.
5.2.3. Advantages of Using External Libraries for Diffing
- Simplicity: They are easy to use and require minimal code.
- Comprehensive: They provide advanced diffing capabilities, including deep diffing and handling of circular references.
- Customization: They provide customization options for specific diffing scenarios.
- Performance: They are optimized for performance.
By using techniques for identifying differences between objects and external libraries for diffing, you can track changes, update data, and debug issues more efficiently and accurately.
6. Real-World Applications of Object Comparison
Object comparison is a fundamental operation in many JavaScript applications. It is used for a wide range of purposes, including data validation, state management, and UI updates. This section will explore some real-world applications of object comparison, including form change detection, state management in React, and API request optimization.
6.1. Form Change Detection
Object comparison can be used to detect changes in form data. This is useful for determining whether a user has made any changes to a form before submitting it.
function formHasChanged(initialValues, currentValues) {
return !_.isEqual(initialValues, currentValues);
}
let initialValues = { name: "John", age: 30, address: { city: "New York", country: "USA" } };
let currentValues = { name: "John", age: 35, address: { city: "Los Angeles", country: "USA" } };
console.log(formHasChanged(initialValues, currentValues)); // true
In this case, the formHasChanged
function compares the initial values of the form with the current values. If the values are different, the function returns true
, indicating that the form has changed.
This can be used to:
- Enable or disable the submit button based on whether changes have been made.
- Prompt the user with a confirmation dialog if they try to navigate away from the form without saving changes.
- Highlight the fields that have been changed.
6.2. State Management in React
Object comparison is used extensively in state management libraries like Redux and Zustand to determine whether a component needs to be re-rendered.
import { useState, useEffect, useRef } from 'react';
import _ from 'lodash';
function MyComponent(props) {
const [state, setState] = useState({ name: "John", age: 30 });
const previousState = useRef(state);
useEffect(() => {
if (!_.isEqual(state, previousState.current)) {
console.log('State has changed. Re-rendering component.');
previousState.current = state;
}
}, [state]);
const updateName = () => {
setState(prevState => ({ ...prevState, name: "Jane" }));
}
return (
<div>
<p>Name: {state.name}</p>
<button onClick={updateName}>Update Name</button>
</div>
);
}
In this example, the useEffect
hook compares the current state with the previous state using _.isEqual
. If the states are different, it logs a message to the console and updates the previousState
ref. This ensures that the component only re-renders when the state has actually changed, improving performance.
6.3. API Request Optimization
Object comparison can be used to optimize API requests by only sending the data that has changed. This can reduce the amount of data transferred over the network and improve the performance of the application.
function getUpdates(initialValues, currentValues) {
const updates = {};
for (let key in currentValues) {
if (currentValues.hasOwnProperty(key) && !_.isEqual(initialValues[key], currentValues[key])) {
updates[key] = currentValues[key];
}
}
return updates;
}
let initialValues = { name: "John", age: 30, address: { city: "New York", country: "USA" } };
let currentValues = { name: "John", age: 35, address: { city: "Los Angeles", country: "USA" }, phone: "123-456-7890" };
let updates = getUpdates(initialValues, currentValues);
console.log(updates);
/*
{
age: 35,
address: { city: "Los Angeles", country: "USA" },
phone: "123-456-7890"
}
*/
// Send only the updates to the API
function sendUpdatesToAPI(updates) {
// Code to send the updates to the API
}
sendUpdatesToAPI(updates);
In this case, the getUpdates
function compares the initial values with the current values and returns an object containing only the properties that have changed. This object can then be sent to the API, reducing the amount of data transferred over the network.
By using object comparison in these real-world applications, you can improve the performance, efficiency, and user experience of your JavaScript applications.
7. Performance Considerations
While object comparison is a fundamental operation in JavaScript, it can be computationally expensive, especially for large objects with deep nesting. This section will explore some performance considerations when comparing objects, including the impact of object size and complexity, and techniques for optimizing object comparison.
7.1. Impact of Object Size and Complexity
The performance of object comparison algorithms is directly affected by the size and complexity of the objects being compared. Larger objects with more properties and deeper nesting require more time and resources to compare.
For example, comparing two objects with 10 properties each will be faster than comparing two objects with 100 properties each. Similarly, comparing two objects with shallow nesting will be faster than comparing two objects with deep nesting.
7.2. Optimizing Object Comparison
Several techniques can be used to optimize object comparison and improve performance.
7.2.1. Short-Circuiting
Short-circuiting involves checking for differences early in the comparison process and returning early if a difference is found. This can avoid unnecessary comparisons of the remaining properties.
function compareObjects(obj1, obj2) {
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return obj1 === obj2;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj