[removed] Deep comparison recursively: Objects and properties

后端 未结 3 1979
别跟我提以往
别跟我提以往 2021-01-13 08:06

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

3条回答
  •  星月不相逢
    2021-01-13 08:43

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

提交回复
热议问题