JavaScript中的对象比较

这个问题在这里已经有了答案:

  • 如何确定两个JavaScript对象的相等性? 49个答案

  • 不幸的是,除非您递归使用_proto_并访问所有非可枚举属性,否则没有完美的方式,但这仅适用于Firefox。

    所以我能做的最好的是猜测使用场景。


    1)快速和有限。

    当你有简单的JSON风格的对象时没有方法和DOM节点:

     JSON.stringify(obj1) === JSON.stringify(obj2) 
    

    该属性的ORDER是重要的,所以这个方法将返回false为以下对象:

     x = {a: 1, b: 2};
     y = {b: 2, a: 1};
    

    2)缓慢和更通用。

    将对象不挖掘到原型中,然后递归比较属性的投影,并比较构造函数。

    这几乎是正确的算法:

    function deepCompare () {
      var i, l, leftChain, rightChain;
    
      function compare2Objects (x, y) {
        var p;
    
        // remember that NaN === NaN returns false
        // and isNaN(undefined) returns true
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
             return true;
        }
    
        // Compare primitives and functions.     
        // Check if both arguments link to the same object.
        // Especially useful on the step where we compare prototypes
        if (x === y) {
            return true;
        }
    
        // Works in case when functions are created in constructor.
        // Comparing dates is a common scenario. Another built-ins?
        // We can even handle functions passed across iframes
        if ((typeof x === 'function' && typeof y === 'function') ||
           (x instanceof Date && y instanceof Date) ||
           (x instanceof RegExp && y instanceof RegExp) ||
           (x instanceof String && y instanceof String) ||
           (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString();
        }
    
        // At last checking prototypes as good as we can
        if (!(x instanceof Object && y instanceof Object)) {
            return false;
        }
    
        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false;
        }
    
        if (x.constructor !== y.constructor) {
            return false;
        }
    
        if (x.prototype !== y.prototype) {
            return false;
        }
    
        // Check for infinitive linking loops
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
             return false;
        }
    
        // Quick checking of one object being a subset of another.
        // todo: cache the structure of arguments[0] for performance
        for (p in y) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            }
            else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
        }
    
        for (p in x) {
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false;
            }
            else if (typeof y[p] !== typeof x[p]) {
                return false;
            }
    
            switch (typeof (x[p])) {
                case 'object':
                case 'function':
    
                    leftChain.push(x);
                    rightChain.push(y);
    
                    if (!compare2Objects (x[p], y[p])) {
                        return false;
                    }
    
                    leftChain.pop();
                    rightChain.pop();
                    break;
    
                default:
                    if (x[p] !== y[p]) {
                        return false;
                    }
                    break;
            }
        }
    
        return true;
      }
    
      if (arguments.length < 1) {
        return true; //Die silently? Don't know how to handle such case, please help...
        // throw "Need two or more arguments to compare";
      }
    
      for (i = 1, l = arguments.length; i < l; i++) {
    
          leftChain = []; //Todo: this can be cached
          rightChain = [];
    
          if (!compare2Objects(arguments[0], arguments[i])) {
              return false;
          }
      }
    
      return true;
    }
    

    已知的问题(当然,它们的优先级很低,可能你永远都不会注意到它们):

  • 具有不同原型结构但投影相同的对象
  • 函数可能具有相同的文本,但指的是不同的关闭
  • 测试:通过测试来自如何确定两个JavaScript对象的相等性?


    这里是我在ES3中的评论解决方案(代码后的细节):

    Object.equals = function( x, y ) {
      if ( x === y ) return true;
        // if both x and y are null or undefined and exactly the same
    
      if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
        // if they are not strictly equal, they both need to be Objects
    
      if ( x.constructor !== y.constructor ) return false;
        // they must have the exact same prototype chain, the closest we can do is
        // test there constructor.
    
      for ( var p in x ) {
        if ( ! x.hasOwnProperty( p ) ) continue;
          // other properties were tested using x.constructor === y.constructor
    
        if ( ! y.hasOwnProperty( p ) ) return false;
          // allows to compare x[ p ] and y[ p ] when set to undefined
    
        if ( x[ p ] === y[ p ] ) continue;
          // if they have the same strict value or identity then they are equal
    
        if ( typeof( x[ p ] ) !== "object" ) return false;
          // Numbers, Strings, Functions, Booleans must be strictly equal
    
        if ( ! Object.equals( x[ p ],  y[ p ] ) ) return false;
          // Objects and Arrays must be tested recursively
      }
    
      for ( p in y ) {
        if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
          // allows x[ p ] to be set to undefined
      }
      return true;
    }
    

    在开发这个解决方案时,我特别关注了角落案例和效率问题,并试图提出一个简单的解决方案,希望能带来一些优雅。 JavaScript允许null未定义的属性和对象具有原型链 ,如果不进行检查, 可能会导致非常不同的行为。

    首先,我选择扩展Object而不是Object.prototype ,主要是因为null不能成为比较的对象之一,我相信null应该是一个有效的对象来与另一个进行比较。 其他人也有其他关于扩展Object.prototype关于其他代码可能产生的副作用的合理担忧。

    必须特别小心处理JavaScript允许将对象属性设置为未定义的可能性,即存在将值设置为未定义的属性。 上述解决方案验证两个对象具有相同的属性设置为undefined以报告相等性。 这只能通过使用Object.hasOwnProperty(property_name)检查属性的存在来完成。 另请注意, JSON.stringify()删除设置为未定义的属性,因此使用此表单进行比较将忽略设置为未定义值的属性。

    只有当它们共享相同的引用时,功能才应被视为相同,而不仅仅是相同的代码,因为这不会考虑这些函数原型。 因此,比较代码字符串不能保证它们具有相同的原型对象。

    这两个对象应该有相同的原型链 ,而不仅仅是相同的属性。 这只能通过比较两个对象的构造函数以进行严格相等来跨浏览器进行测试。 ECMAScript 5将允许使用Object.getPrototypeOf()来测试它们的实际原型。 一些网络浏览器也提供了一个__proto__属性来完成同样的事情。 上述代码的一个可能的改进将允许在可用时使用这些方法之一。

    运用严格的比较,是这里最重要的,因为2不应被视为等于“2.0000”,也不是 ,应考虑为null, 未定义0。

    效率方面的考虑使我尽快对财产的平等性进行比较。 然后,只有失败,认准的typeof这些属性。 对于具有许多标量属性的大型对象,速度提升可能非常重要。

    不再需要两个循环,第一个检查左侧对象的属性,第二个检查右侧的属性并验证唯一的存在(不是值),以捕获这些用未定义值定义的属性。

    总体来说,这段代码只处理16行代码(没有注释)处理大多数转角情况。

    更新(8/13/2015) 。 我已经实现了一个更好的版本,因为函数value_equals()更快,可以正确处理NaN和0等不同于-0的角落案例,可以强制执行对象的属性顺序和测试循环引用,并支持超过100个自动化测试作为Toubkal项目测试套件的一部分。


    当然不是唯一的方法 - 你可以创建一个方法(针对Object,但我当然不会建议使用Object作为实时代码)来复制C#/ Java风格比较方法。

    编辑,因为一个普遍的例子似乎预计:

    Object.prototype.equals = function(x)
    {
        for(p in this)
        {
            switch(typeof(this[p]))
            {
                case 'object':
                    if (!this[p].equals(x[p])) { return false }; break;
                case 'function':
                    if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
                default:
                    if (this[p] != x[p]) { return false; }
            }
        }
    
        for(p in x)
        {
            if(typeof(this[p])=='undefined') {return false;}
        }
    
        return true;
    }
    

    请注意,使用toString()的测试方法绝对不够好,但由于空白有意义或无意义的问题,一个可接受的方法非常困难,不用介意在不同实现中产生相同结果的同义方法和方法。 一般来说,针对Object的原型设计的问题。

    链接地址: http://www.djcxy.com/p/4695.html

    上一篇: Object comparison in JavaScript

    下一篇: How to deep merge instead of shallow merge?