首页 文章

JavaScript中的对象比较[重复]

提问于
浏览
831

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

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

例:

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

我知道 two objects are equal if they refer to the exact same object ,但有没有办法检查它们是否具有相同的属性值?

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

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

10 回答

  • 991

    不幸的是,没有完美的方法,除非你递归使用 _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;
    }
    

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

    • 具有不同原型结构但投影相同的对象

    • 函数可能具有相同的文本但引用了不同的闭包

    Tests: 通过测试来自How to determine equality for two JavaScript objects? .

  • 157

    这是我在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 属性和对象具有 prototypes chains ,如果不进行检查,可能导致非常不同的行为 .

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

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

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

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

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

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

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

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

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

  • 20
    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对象的简单方法 .

  • 5

    当然不是唯一的方法 - 你可以原型化一个方法(这里反对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的原型设计问题 .

  • 11

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

    对象被认为是等效的

    • 它们完全相同 === (字符串和数字首先被解包以确保 42 相当于 Number(42)

    • 或者它们都是日期并且具有相同的 valueOf()

    • 或它们都是相同的类型,而不是null和...

    • 它们不是对象,并且相等 == (捕获数字/字符串/布尔值)

    • 或忽略具有 undefined 值的属性,它们具有相同的属性,所有这些属性都被认为是递归等效的 .

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

    避免了可能由圆形数据结构引起的 Infinite loops . 当 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;
        }
    }
    
  • 15

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

    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;}));
    
  • 4

    我修改了上面的代码 . 对我来说0!== false和null!== undefined . 如果您不需要这样严格的检查,请在代码中删除一个“= " sign in " 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;
    }
    

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

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

    a == b预期为真;回来了

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

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

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

    f == g预期为真;回来了

    h == g预期错误;返回假

    i == j预期为真;回来了

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

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

  • 22

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

  • 3

    这是我的版本,这个线程中的很多内容都是集成的(测试用例的计数相同):

    Object.defineProperty(Object.prototype, "equals", {
        enumerable: false,
        value: function (obj) {
            var p;
            if (this === obj) {
                return true;
            }
    
            // some checks for native types first
    
            // function and sring
            if (typeof(this) === "function" || typeof(this) === "string" || this instanceof String) { 
                return this.toString() === obj.toString();
            }
    
            // number
            if (this instanceof Number || typeof(this) === "number") {
                if (obj instanceof Number || typeof(obj) === "number") {
                    return this.valueOf() === obj.valueOf();
                }
                return false;
            }
    
            // null.equals(null) and undefined.equals(undefined) do not inherit from the 
            // Object.prototype so we can return false when they are passed as obj
            if (typeof(this) !== typeof(obj) || obj === null || typeof(obj) === "undefined") {
                return false;
            }
    
            function sort (o) {
                var result = {};
    
                if (typeof o !== "object") {
                    return o;
                }
    
                Object.keys(o).sort().forEach(function (key) {
                    result[key] = sort(o[key]);
                });
    
                return result;
            }
    
            if (typeof(this) === "object") {
                if (Array.isArray(this)) { // check on arrays
                    return JSON.stringify(this) === JSON.stringify(obj);                
                } else { // anyway objects
                    for (p in this) {
                        if (typeof(this[p]) !== typeof(obj[p])) {
                            return false;
                        }
                        if ((this[p] === null) !== (obj[p] === null)) {
                            return false;
                        }
                        switch (typeof(this[p])) {
                        case 'undefined':
                            if (typeof(obj[p]) !== 'undefined') {
                                return false;
                            }
                            break;
                        case 'object':
                            if (this[p] !== null 
                                    && obj[p] !== null 
                                    && (this[p].constructor.toString() !== obj[p].constructor.toString() 
                                            || !this[p].equals(obj[p]))) {
                                return false;
                            }
                            break;
                        case 'function':
                            if (this[p].toString() !== obj[p].toString()) {
                                return false;
                            }
                            break;
                        default:
                            if (this[p] !== obj[p]) {
                                return false;
                            }
                        }
                    };
    
                }
            }
    
            // at least check them with JSON
            return JSON.stringify(sort(this)) === JSON.stringify(sort(obj));
        }
    });
    

    这是我的TestCase:

    assertFalse({}.equals(null));
        assertFalse({}.equals(undefined));
    
        assertTrue("String", "hi".equals("hi"));
        assertTrue("Number", new Number(5).equals(5));
        assertFalse("Number", new Number(5).equals(10));
        assertFalse("Number+String", new Number(1).equals("1"));
    
        assertTrue([].equals([]));
        assertTrue([1,2].equals([1,2]));
        assertFalse([1,2].equals([2,1]));
        assertFalse([1,2].equals([1,2,3]));
    
        assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
        assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));
    
        assertTrue({}.equals({}));
        assertTrue({a:1,b:2}.equals({a:1,b:2}));
        assertTrue({a:1,b:2}.equals({b:2,a:1}));
        assertFalse({a:1,b:2}.equals({a:1,b:3}));
    
        assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
        assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));
    
        assertTrue("Function", (function(x){return x;}).equals(function(x){return x;}));
        assertFalse("Function", (function(x){return x;}).equals(function(y){return y+2;}));
    
        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};
    
        assertTrue(a.equals(b));
        assertFalse(a.equals(c));
        assertFalse(c.equals(d));
        assertFalse(a.equals(e));
        assertTrue(f.equals(g));
        assertFalse(h.equals(g));
        assertTrue(i.equals(j));
        assertFalse(d.equals(k));
        assertFalse(k.equals(l));
    
  • 4

    如果您在没有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
    

相关问题