Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: Deep comparison recursively: Objects and properties

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. 
      }
    }
like image 572
tsaiDavid Avatar asked May 27 '15 07:05

tsaiDavid


People also ask

Can we compare two objects in JavaScript?

We cannot just implement “==” or “===” operator to compare two objects. The better way to do the comparison is to use JSON. Stringify and compare the objects.

How do you do deep comparison in JavaScript?

Using the isEqual() method from this library, we can perform a deep comparison between the given operands. It will return a Boolean value indicating whether the operands are equal based on JavaScript strict equality (===) on all attributes of the two given objects.

What is deeply equal in JavaScript?

deepEqual() method tests if two objects, and their child objects, are equal, using the == operator. If the two objects are not equal, an assertion failure is being caused, and the program is terminated. To compare the objects using the === operator, use the assert.

How many ways can you compare two objects in JavaScript?

JavaScript provides 3 ways to compare values: The strict equality operator === The loose equality operator == Object.is() function.


1 Answers

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;
}
like image 192
RobG Avatar answered Nov 07 '22 20:11

RobG