协慌网

登录 贡献 社区

JavaScript 中的对象比较

在 JavaScript 中比较对象的最佳方法是什么?

例:

var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false

我知道如果它们引用完全相同的对象则两个对象是相等的 ,但有没有办法检查它们是否具有相同的属性值?

以下方式对我有用,但这是唯一的可能性吗?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true

答案

不幸的是,没有完美的方法,除非你使用递归的_proto_并访问所有不可枚举的属性,但这只适用于 Firefox。

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


1)快速且有限。

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

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 允许nullundefined属性,对象具有原型链 ,如果不进行检查,可能会导致非常不同的行为。

首先,我选择扩展Object而不是Object.prototype ,主要是因为null不能成为比较的对象之一,并且我认为null应该是一个有效的对象来与另一个进行比较。其他人也注意到关于Object.prototype的扩展关于其他代码的可能副作用的其他合理问题。

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

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

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

严格比较的使用在这里是至关重要的,因为2不应该被认为等于“2.0000” ,也不应该认为false等于nullundefined0

效率考虑使我能够尽快比较属性的平等性。然后,只有失败,认准的 typeof这些属性。对于具有大量标量属性的大型对象,速度提升可能很重要。

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

总体而言,此代码仅处理 16 行代码中的大多数极端情况(无注释)。

更新(2015 年 8 月 13 日) 。我已经实现了一个更好的版本,因为函数value_equals()更快,处理正确的极端情况,如 NaN 和 0 不同于 - 0,可选地强制执行对象的属性顺序和测试循环引用,由100 多个自动化测试支持作为Toubkal项目测试套件的一部分。

Utils.compareObjects = function(o1, o2){
    for(var p in o1){
        if(o1.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    for(var p in o2){
        if(o2.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    return true;
};

比较 ONE-LEVEL 对象的简单方法。