admin管理员组文章数量:1188003
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 the use of a recursive call. I know my solution below is quite naive and a bit bulky, but I'm trying to wrap my head around all these new things I'm still learning! Just less than a month in on programming :) I would appreciate any tips and help you might have in improving the code and also if you could help me better understand the recursion that needs to occur. Thank you in advance!
- Question (Eloquent JS 2nd Ed, Chapter 4, Exercise 4):
Write a function, deepEqual, that takes two values and returns true only if they are the same value or are objects with the same properties whose values are also equal when compared with a recursive call to deepEqual.
var obj = {here: {is: "an"}, object: 2};
var obj1 = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj,obj1));
My Code:
function objectTester(x) {
if (typeof x === 'object' && x !== null)
return true;
}
function deepEqual(valOne, valTwo) {
if (valOne === valTwo) return true;
var comp1 = objectTester(valOne);
var comp2 = objectTester(valTwo);
if (comp1 === comp2) {
var count1;
var count2;
for (var prop in valOne) {
count1++
return count1;
}
for (var prop in valTwo) {
count2++
return count2;
}
if (count1 === count2) {
// This is where I'm getting stuck, not sure how I can recurisvely compare
// two arguments that are to be compared.
}
}
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 the use of a recursive call. I know my solution below is quite naive and a bit bulky, but I'm trying to wrap my head around all these new things I'm still learning! Just less than a month in on programming :) I would appreciate any tips and help you might have in improving the code and also if you could help me better understand the recursion that needs to occur. Thank you in advance!
- Question (Eloquent JS 2nd Ed, Chapter 4, Exercise 4):
Write a function, deepEqual, that takes two values and returns true only if they are the same value or are objects with the same properties whose values are also equal when compared with a recursive call to deepEqual.
var obj = {here: {is: "an"}, object: 2};
var obj1 = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj,obj1));
My Code:
function objectTester(x) {
if (typeof x === 'object' && x !== null)
return true;
}
function deepEqual(valOne, valTwo) {
if (valOne === valTwo) return true;
var comp1 = objectTester(valOne);
var comp2 = objectTester(valTwo);
if (comp1 === comp2) {
var count1;
var count2;
for (var prop in valOne) {
count1++
return count1;
}
for (var prop in valTwo) {
count2++
return count2;
}
if (count1 === count2) {
// This is where I'm getting stuck, not sure how I can recurisvely compare
// two arguments that are to be compared.
}
}
Share
Improve this question
asked May 27, 2015 at 7:44
tsaiDavidtsaiDavid
3552 gold badges6 silver badges14 bronze badges
5
- 1 You only want to consider own properties, so a for..in solution should test that. Or you can use Object.keys. – RobG Commented May 27, 2015 at 8:03
- 2 You might want to look at Object comparison in JavaScript and How do you determine equality for two JavaScript objects?. – RobG Commented May 27, 2015 at 8:46
- Thanks @RobG - the Eloquent JS solution had suggested that I actually count the properties in order to see if they even share that - I suppose I can use the keys to do so? – tsaiDavid Commented May 27, 2015 at 16:47
- 1 Yes, the code in my answer does that. – RobG Commented May 28, 2015 at 6:22
- I have proposed a variant that make the comparison, while returning the nested keys equality as result - stackoverflow.com/questions/55591096/… – loretoparisi Commented Apr 9, 2019 at 10:54
4 Answers
Reset to default 19Probably 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;
}
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]); });
}
I think all given answers are amazing.
But for not so experts eyes I will like to make reading easier clarifying how the solutions that use Array.prototype.every
works for nested arrays.
It turns out that the keys method from the global Object has a not so commonly used capability where receive an array as parameter and
returns an array whose elements are strings corresponding to the enumerable properties found directly upon
object
.
console.log(typeof [] === 'object') // true
Object.keys([true, { a:'a', b: 'b' }, 2, null]) // ['0', '1', '2', '3']
So this is how Object.keys().every() works fine for function arguments, indexing {}s and []s, either with property keys or array indexes.
Hope help someone.
本文标签: recursionJavaScript Deep comparison recursively Objects and propertiesStack Overflow
版权声明:本文标题:recursion - JavaScript: Deep comparison recursively: Objects and properties - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738353682a2079446.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论