Today I finished reading Ch. 4 in Eloquent JS and I\'m struggling to understand how to perform a deep comparison between objects and their properties, particularly through t
You can use below code for deep comparison -
const isEqual = (a, b) => {
if (a === b) return true;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
if (a === null || a === undefined || b === null || b === undefined) return false;
if (a.prototype !== b.prototype) return false;
let keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) return false;
return keys.every(k => isEqual(a[k], b[k]));
};
example -
isEqual({ prop1: [2, { e: 3 }], prop2: [4], prop3: 'foo' }, { prop1: [2, { e: 3 }], prop2: [4], prop3: 'foo' }); // true
Check the code below. That should work for you.
function objectEquals(x, y) {
'use strict';
if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
// after this just checking type of one would be enough
if (x.constructor !== y.constructor) { return false; }
// if they are functions, they should exactly refer to same one (because of closures)
if (x instanceof Function) { return x === y; }
// if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
if (x instanceof RegExp) { return x === y; }
if (x === y || x.valueOf() === y.valueOf()) { return true; }
if (Array.isArray(x) && x.length !== y.length) { return false; }
// if they are dates, they must had equal valueOf
if (x instanceof Date) { return false; }
// if they are strictly equal, they both need to be object at least
if (!(x instanceof Object)) { return false; }
if (!(y instanceof Object)) { return false; }
// recursive object equality check
var p = Object.keys(x);
return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
p.every(function (i) { return objectEquals(x[i], y[i]); });
}
Probably easiest to just post a function that does the job with plenty of comments. The recursive part is near the bottom, in the function passed to every:
// Helper to return a value's internal object [[Class]]
// That this returns [object Type] even for primitives
function getClass(obj) {
return Object.prototype.toString.call(obj);
}
/*
** @param a, b - values (Object, RegExp, Date, etc.)
** @returns {boolean} - true if a and b are the object or same primitive value or
** have the same properties with the same values
*/
function objectTester(a, b) {
// If a and b reference the same value, return true
if (a === b) return true;
// If a and b aren't the same type, return false
if (typeof a != typeof b) return false;
// Already know types are the same, so if type is number
// and both NaN, return true
if (typeof a == 'number' && isNaN(a) && isNaN(b)) return true;
// Get internal [[Class]]
var aClass = getClass(a);
var bClass = getClass(b)
// Return false if not same class
if (aClass != bClass) return false;
// If they're Boolean, String or Number objects, check values
if (aClass == '[object Boolean]' || aClass == '[object String]' || aClass == '[object Number]') {
return a.valueOf() == b.valueOf();
}
// If they're RegExps, Dates or Error objects, check stringified values
if (aClass == '[object RegExp]' || aClass == '[object Date]' || aClass == '[object Error]') {
return a.toString() == b.toString();
}
// Otherwise they're Objects, Functions or Arrays or some kind of host object
if (typeof a == 'object' || typeof a == 'function') {
// For functions, check stringigied values are the same
// Almost certainly false if a and b aren't trivial
// and are different functions
if (aClass == '[object Function]' && a.toString() != b.toString()) return false;
var aKeys = Object.keys(a);
var bKeys = Object.keys(b);
// If they don't have the same number of keys, return false
if (aKeys.length != bKeys.length) return false;
// Check they have the same keys
if (!aKeys.every(function(key){return b.hasOwnProperty(key)})) return false;
// Check key values - uses ES5 Object.keys
return aKeys.every(function(key){
return objectTester(a[key], b[key])
});
}
return false;
}
The tests on Date, RegExp, Error, etc. should probably return false if the values/strings aren't the same then fall through to the property checks, but do that only if you think someone might attach properties to Number objects (it's extremely rare to use Number objects, much less add properties to them, but I guess it might happen).
Here it is:
/*
** @param a, b - values (Object, RegExp, Date, etc.)
** @returns {boolean} - true if a and b are the object or same primitive value or
** have the same properties with the same values
*/
function objectTester(a, b) {
// If a and b reference the same value, return true
if (a === b) return true;
// If a and b aren't the same type, return false
if (typeof a != typeof b) return false;
// Already know types are the same, so if type is number
// and both NaN, return true
if (typeof a == 'number' && isNaN(a) && isNaN(b)) return true;
// Get internal [[Class]]
var aClass = getClass(a);
var bClass = getClass(b)
// Return false if not same class
if (aClass != bClass) return false;
// If they're Boolean, String or Number objects, check values
if (aClass == '[object Boolean]' || aClass == '[object String]' || aClass == '[object Number]') {
if (a.valueOf() != b.valueOf()) return false;
}
// If they're RegExps, Dates or Error objects, check stringified values
if (aClass == '[object RegExp]' || aClass == '[object Date]' || aClass == '[object Error]') {
if (a.toString() != b.toString()) return false;
}
// For functions, check stringigied values are the same
// Almost impossible to be equal if a and b aren't trivial
// and are different functions
if (aClass == '[object Function]' && a.toString() != b.toString()) return false;
// For all objects, (including Objects, Functions, Arrays and host objects),
// check the properties
var aKeys = Object.keys(a);
var bKeys = Object.keys(b);
// If they don't have the same number of keys, return false
if (aKeys.length != bKeys.length) return false;
// Check they have the same keys
if (!aKeys.every(function(key){return b.hasOwnProperty(key)})) return false;
// Check key values - uses ES5 Object.keys
return aKeys.every(function(key){
return objectTester(a[key], b[key])
});
return false;
}