在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

当前回答

如果要显式检查方法,可以使用method.toSource()或method.toString()方法。

其他回答

如果您在没有JSON库的情况下工作,也许这将帮助您:

Object.prototype.equals = function(b) {
    var a = this;
    for(i in a) {
        if(typeof b[i] == 'undefined') {
            return false;
        }
        if(typeof b[i] == 'object') {
            if(!b[i].equals(a[i])) {
                return false;
            }
        }
        if(b[i] != a[i]) {
            return false;
        }
    }
    for(i in b) {
        if(typeof a[i] == 'undefined') {
            return false;
        }
        if(typeof a[i] == 'object') {
            if(!a[i].equals(b[i])) {
                return false;
            }
        }
        if(a[i] != b[i]) {
            return false;
        }
    }
    return true;
}

var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false

不幸的是,没有完美的方法,除非您递归地使用_proto_并访问所有非枚举财产,但这只适用于Firefox。

因此,我所能做的就是猜测使用场景。


1) 快速且有限。

当您有简单的JSON样式的对象,但内部没有方法和DOM节点时,可以使用:

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

财产的顺序很重要,因此此方法将为以下对象返回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对象的相等性?。

以下算法将处理自引用数据结构、数字、字符串、日期,当然还有普通的嵌套javascript对象:

根据==,它们完全相等(字符串和数字首先展开,以确保42等于数字(42))或者它们都是日期并且具有相同的值Of()或者它们都是同一类型且不是空的。。。它们不是对象,每个==都相等(捕获数字/字符串/布尔值)或者,忽略具有未定义值的财产,它们具有相同的财产,所有这些属性都被视为递归等价。

函数文本不认为函数相同。此测试不够,因为函数可能具有不同的闭包。只有当==这样说时,函数才被认为是相等的(但如果您选择这样做,您可以很容易地扩展等效关系)。

避免了循环数据结构可能导致的无限循环。当areEquivalent试图反驳等式并递归到对象的财产中时,它会跟踪需要进行此子比较的对象。如果等式可以被否定,那么对象之间的某些可达属性路径不同,那么必须有一条最短的可达路径,并且该最短可达路径不能包含两条路径中存在的循环;即在递归比较对象时假设相等是可以的。假设存储在属性areEquivalent_Eq_91_2_34中,该属性在使用后被删除,但如果对象图已经包含此类属性,则行为未定义。使用这样的标记属性是必要的,因为javascript不支持使用任意对象作为键的字典。

function unwrapStringOrNumber(obj) {
    return (obj instanceof Number || obj instanceof String 
            ? obj.valueOf() 
            : obj);
}
function areEquivalent(a, b) {
    a = unwrapStringOrNumber(a);
    b = unwrapStringOrNumber(b);
    if (a === b) return true; //e.g. a and b both null
    if (a === null || b === null || typeof (a) !== typeof (b)) return false;
    if (a instanceof Date) 
        return b instanceof Date && a.valueOf() === b.valueOf();
    if (typeof (a) !== "object") 
        return a == b; //for boolean, number, string, xml

    var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
        newB = (b.areEquivalent_Eq_91_2_34 === undefined);
    try {
        if (newA) a.areEquivalent_Eq_91_2_34 = [];
        else if (a.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === b; })) return true;
        if (newB) b.areEquivalent_Eq_91_2_34 = [];
        else if (b.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === a; })) return true;
        a.areEquivalent_Eq_91_2_34.push(b);
        b.areEquivalent_Eq_91_2_34.push(a);

        var tmp = {};
        for (var prop in a) 
            if(prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;
        for (var prop in b) 
            if (prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;

        for (var prop in tmp) 
            if (!areEquivalent(a[prop], b[prop]))
                return false;
        return true;
    } finally {
        if (newA) delete a.areEquivalent_Eq_91_2_34;
        if (newB) delete b.areEquivalent_Eq_91_2_34;
    }
}

如果要显式检查方法,可以使用method.toSource()或method.toString()方法。

  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对象的简单方法。