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.

Test Cases:

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.

Test Cases:

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
Add a comment  | 

4 Answers 4

Reset to default 19

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;
}

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