JavaScript中的对象比较

spa*_*r79 1021 javascript comparison object object-comparison

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

例:

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

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

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

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
Run Code Online (Sandbox Code Playgroud)

cra*_*zyx 1215

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

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


1)快速且有限.

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

 JSON.stringify(obj1) === JSON.stringify(obj2) 
Run Code Online (Sandbox Code Playgroud)

属性的ORDER是重要的,因此此方法将对以下对象返回false:

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

已知问题(嗯,它们的优先级非常低,可能你永远不会注意到它们):

  • 具有不同原型结构但投影相同的物体
  • 函数可能具有相同的文本但引用不同的闭包

测试:传递测试来自如何确定两个JavaScript对象的相等性?.

  • 如果可以避免,则不应扩展Object.prototype.如果没有`if(!someObject.hasOwnProperty(key))继续的话,它会导致丑陋的问题,比如打破`for(在someObject中的var键)```. (27认同)
  • 我认为你应该使用*相同的*比较运算符:`===`,因为`{a:5}`和`{a:"5.0"}`不相等,或者是它们? (17认同)
  • 关于**1)***"属性的ORDER是重要的,因此这个方法将对以下对象返回false:"*这不一定是真的.那个方法***可能会为这些对象返回"false",或者可能不会.无论如何都无法保证.这就是为什么我们不使用`JSON.stringify`比较进行对象比较.没有订单保证. (15认同)
  • 当定义属性但设置为`undefined`值时,对`undefined`的检查将失败.使用`in`运算符而不是`typeof`来避免这种情况:`p in x`.另外,通过字符串值比较函数是非常不可靠的.除了函数分解失败的通常原因之外,两个函数具有相同的代码但由于闭包而具有非常不同的行为也是很常见的.例如.由jQuery的`$ .proxy`或Prototype的`Function#bind`创建的任何函数.我只是坚持比较功能身份. (12认同)
  • 函数比较错误:函数可能具有相同的文本但引用不同的闭包.最好只返回`this [p] === x [p]`. (11认同)
  • 如果对象树包含循环,则会导致堆栈溢出. (5认同)
  • @OweRReLoaDeD似乎有点过分来引入一个完整的框架来做一个简单的比较. (4认同)
  • 虽然我认为这个解决方案很好,但我认为扩展本地对象是一种不好的做法,就像你在这里做的那样.将它作为一个单独的功能会更好.您甚至可以保留当前函数,但使用call:like equals.call(x,y); (4认同)
  • 这是太多的工作使用Underscore.js isEqual mehtod http://underscorejs.org/#isEqual (3认同)
  • -1,而不是我想听的(开玩笑).这是正确的Dict(Python)或Map(ES6)与一般对象之间差异的主要示例.我现在将重新设计一个模块,以避免不得不处理这个问题. (2认同)
  • 使用有序的stringify赋予权力(1).对于有序的stringify,使用`JSON.stringify(obj,function(key,value){if(value instanceof Object &&!Array.isArray(value)){return Object.keys(value).sort().reduce(function(function) ret,key){ret [key] = value [key]; return ret;},{});} return value;});` (2认同)

Jea*_*ent 178

这是我在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;
}
Run Code Online (Sandbox Code Playgroud)

在开发这个解决方案的过程中,我特别考虑了角落的情况,效率,但试图提出一个有效的简单解决方案,希望有一些优雅.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等于null,undefined0.

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

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

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

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

  • 我在我的代码中使用此功能.刚发现它在测试循环对象时耗尽了堆栈.我没有实现它,因为我现在不需要它,但是想让你知道.http://jsfiddle.net/mendesjuan/uKtEy/ (4认同)
  • @JuanMendes,如果你需要一个测试循环对象的版本,一个更有效的方法是将循环测试合并到Object:equals(),以便在递归之前只测试一个属性:更新小提琴这里http:/ /jsfiddle.net/uKtEy/3/ (2认同)

小智 28

  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;
};
Run Code Online (Sandbox Code Playgroud)

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

  • hasOwnProperty确保“ p”不是函数。 (2认同)

ann*_*ata 21

当然不是唯一的方法 - 你可以原型化一个方法(这里反对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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,使用toString()的测试方法绝对不够好,但是由于空白具有或不具有意义的问题,一个可接受的方法非常困难,更不用说使用不同实现产生相同结果的同义词方法和方法.以及针对Object的原型设计问题.

  • 你只是在循环"这个"元素.当"this"是较大"x"的子集时,你的equals方法不会返回true吗?你不应该也加一个for(p in x)循环吗? (3认同)
  • 代码很麻烦.如果o1的所有属性都等于o2的属性,则o1.equals(o2)返回true.但是如果o2还具有o1没有的其他属性,则对象可能被错误地标记为相等. (2认同)

Eam*_*nne 18

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

当对象被认为是等价的

  • 它们完全相等===(字符串和数字首先被打开,以确保42相当于Number(42))
  • 或者它们都是日期并且具有相同的 valueOf()
  • 或者它们都是相同的类型而不是null并且......
    • 它们不是对象并且是相等的==(捕获数字/字符串/布尔值)
    • 或者,忽略具有undefined值的属性,它们具有相同的属性,所有这些属性都被认为是递归等效的.

功能不被认为是由功能文本一致.此测试不充分,因为功能可能有不同的闭包.如果===这样说,函数只被视为相等(但如果您选择这样做,您可以轻松扩展该等效关系).

避免了可能由圆形数据结构引起的无限循环.当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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Dan仅仅防止无限递归是不够的:你仍然想知道周期性对象是否相等. (6认同)

mho*_*oms 12

我写了这段代码用于对象比较,它似乎工作.检查断言:


function countProps(obj) {
    var count = 0;
    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            count++;
        }
    }
    return count;
};

function objectEquals(v1, v2) {

    if (typeof(v1) !== typeof(v2)) {
        return false;
    }

    if (typeof(v1) === "function") {
        return v1.toString() === v2.toString();
    }

    if (v1 instanceof Object && v2 instanceof Object) {
        if (countProps(v1) !== countProps(v2)) {
            return false;
        }
        var r = true;
        for (k in v1) {
            r = objectEquals(v1[k], v2[k]);
            if (!r) {
                return false;
            }
        }
        return true;
    } else {
        return v1 === v2;
    }
}

assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

assert.isTrue(objectEquals(function(x){return x;},function(x){return x;}));
assert.isFalse(objectEquals(function(x){return x;},function(y){return y+2;}));
Run Code Online (Sandbox Code Playgroud)


Jev*_*ski 6

我修改了上面的代码.对我来说0!== falsenull!== undefined.如果您不需要这样严格的检查,请在代码中删除" this [p]!== x [p] " 中的一个" = "符号.

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

然后我用下一个对象测试了它:

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};
Run Code Online (Sandbox Code Playgroud)

a == b预期为真; 返回true

a == c预期为假; 返回假

c == d预期为假; 返回假

a == e预期错误; 返回假

f == g预期为真; 返回true

h == g预期为假; 返回假

i == j预期为真; 返回true

d == k预期为假; 返回假

k == l预期为假; 返回假