如何确定两个javascript对象的相等性?

How to determine equality for two JavaScript objects?

严格的相等运算符将告诉您两个对象类型是否相等。但是,有没有一种方法可以判断两个对象是否相等,就像Java中的哈希代码值一样吗?

堆栈溢出问题javascript中有没有任何类型的hashcode函数?与此问题类似,但需要更学术的回答。上面的场景演示了为什么需要一个解决方案,我想知道是否有任何等效的解决方案。


为什么要重新发明轮子?尝试一下罗达什。它有许多必须具有的函数,如isequal()。

1
_.isEqual(object, other);

它将使用ecmascript 5和本机优化(如果浏览器中提供)来强制检查每个键值,就像本页上的其他示例一样。

注意:以前这个答案建议使用underline.js,但是lodash在修复错误和解决一致性问题方面做得更好。


简短的回答

简单的答案是:不,没有一般的方法来确定一个对象在你所指的意义上等于另一个对象。例外是当你严格地认为一个对象是无类型的。

长的答案

这个概念是一个equals方法的概念,它比较一个对象的两个不同实例,以指示它们在值级别上是否相等。但是,具体的类型决定了如何实现Equals方法。对具有原始值的属性进行迭代比较可能不够,很可能存在不被视为对象值一部分的属性。例如,

1
2
3
4
5
6
7
8
 function MyClass(a, b)
 {
     var c;
     this.getCLazy = function() {
         if (c === undefined) c = a * b // imagine * is really expensive
         return c;
     }
  }

在上述情况下,确定myclass的任何两个实例是否相等并不重要,只有ab是重要的。在某些情况下,c可能因实例而异,但在比较过程中并不显著。

注:当成员本身也可能是某一类型的实例,并且这些实例都需要有确定平等的方法时,此问题适用。

更复杂的是,在JavaScript中,数据和方法之间的区别变得模糊。

对象可以引用将作为事件处理程序调用的方法,这可能不被视为其"值状态"的一部分。而另一个对象很可能被分配一个执行重要计算的函数,从而使这个实例与其他对象不同,仅仅是因为它引用了一个不同的函数。

如果一个对象的现有原型方法中有一个被另一个函数重写了,该怎么办?它是否仍然被认为等同于另一个实例,否则它是相同的?对于每种类型,只能在每种特定情况下回答该问题。

如前所述,异常将是一个严格的无类型对象。在这种情况下,唯一明智的选择是对每个成员进行迭代和递归比较。即使这样,我们也要问函数的"值"是什么?


当对象引用内存中的相同位置时,javascript中对象的默认相等运算符将生成true。

1
2
3
4
5
6
var x = {};
var y = {};
var z = x;

x === y; // => false
x === z; // => true

如果您需要一个不同的相等运算符,您需要向类中添加一个equals(other)方法,或者类似的方法,问题域的细节将决定这究竟意味着什么。

下面是一个扑克牌示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Card(rank, suit) {
  this.rank = rank;
  this.suit = suit;
  this.equals = function(other) {
     return other.rank == this.rank && other.suit == this.suit;
  };
}

var queenOfClubs = new Card(12,"C");
var kingOfSpades = new Card(13,"S");

queenOfClubs.equals(kingOfSpades); // => false
kingOfSpades.equals(new Card(13,"S")); // => true


如果在AngularJS中工作,angular.equals函数将确定两个对象是否相等。在ember.js中使用isEqual

  • angular.equals—有关此方法的更多信息,请参阅文档或源代码。它也对数组进行了深入的比较。
  • ember.js isEqual—有关此方法的更多信息,请参阅文档或源代码。它不会对数组进行深度比较。

1
2
3
4
5
6
var purple = [{"purple":"drank"}];
var drank = [{"purple":"drank"}];

if(angular.equals(purple, drank)) {
    document.write('got dat');
}
1
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js">


这是我的版本。它正在使用ES5中引入的新object.keys功能以及来自+、+和+的想法/测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
function objectEquals(x, y) {
    'use strict';

    if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
    // after this just checking type of one would be enough
    if (x.constructor !== y.constructor) { return false; }
    // if they are functions, they should exactly refer to same one (because of closures)
    if (x instanceof Function) { return x === y; }
    // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
    if (x instanceof RegExp) { return x === y; }
    if (x === y || x.valueOf() === y.valueOf()) { return true; }
    if (Array.isArray(x) && x.length !== y.length) { return false; }

    // if they are dates, they must had equal valueOf
    if (x instanceof Date) { return false; }

    // if they are strictly equal, they both need to be object at least
    if (!(x instanceof Object)) { return false; }
    if (!(y instanceof Object)) { return false; }

    // recursive object equality check
    var p = Object.keys(x);
    return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
        p.every(function (i) { return objectEquals(x[i], y[i]); });
}


///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking"Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
    if (x) { document.write('Passed'); }
    else { document.write('Failed'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isFalse(objectEquals(/abc/, /abc/));
assert.isFalse(objectEquals(/abc/, /123/));
var r = /abc/;
assert.isTrue(objectEquals(r, r));

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

Object.prototype.equals = function (obj) { return objectEquals(this, obj); };
var assertFalse = assert.isFalse,
    assertTrue = assert.isTrue;

assertFalse({}.equals(null));
assertFalse({}.equals(undefined));

assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(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}}));

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 i = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
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(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));

// from comments on stackoverflow post
assert.isFalse(objectEquals([1, 2, undefined], [1, 2]));
assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 }));
assert.isFalse(objectEquals(new Date(1234), 1234));

// no two different function is equal really, they capture their context variables
// so even if they have same toString(), they won't have same functionality
var func = function (x) { return true; };
var func2 = function (x) { return true; };
assert.isTrue(objectEquals(func, func));
assert.isFalse(objectEquals(func, func2));
assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));


如果您使用的是JSON库,那么可以将每个对象编码为JSON,然后比较得到的字符串是否相等。

1
2
3
4
var obj1={test:"value"};
var obj2={test:"value2"};

alert(JSON.encode(obj1)===JSON.encode(obj2));

注意:虽然这个答案在许多情况下都有效,但正如一些人在评论中指出的那样,由于各种原因,它是有问题的。在几乎所有情况下,您都希望找到一个更健壮的解决方案。


短功能deepEqual实施:

1
2
3
4
5
6
7
function deepEqual(x, y) {
  return (x && y && typeof x === 'object' && typeof y === 'object') ?
    (Object.keys(x).length === Object.keys(y).length) &&
      Object.keys(x).reduce(function(isEqual, key) {
        return isEqual && deepEqual(x[key], y[key]);
      }, true) : (x === y);
}

编辑:版本2,使用起重臂建议和ES6箭头功能:

1
2
3
4
5
6
7
function deepEqual(x, y) {
  const ok = Object.keys, tx = typeof x, ty = typeof y;
  return x && y && tx === 'object' && tx === ty ? (
    ok(x).length === ok(y).length &&
      ok(x).every(key => deepEqual(x[key], y[key]))
  ) : (x === y);
}


如果您方便使用深度复制功能,则可以使用以下技巧在匹配属性顺序时仍然使用JSON.stringify

1
2
3
4
5
6
7
function equals(obj1, obj2) {
    function _equals(obj1, obj2) {
        return JSON.stringify(obj1)
            === JSON.stringify($.extend(true, {}, obj1, obj2));
    }
    return _equals(obj1, obj2) && _equals(obj2, obj1);
}

演示:http://jsfiddle.net/cu3vb/3/

理论基础:

由于将obj1的属性逐个复制到克隆中,因此将保留它们在克隆中的顺序。并且当将obj2的属性复制到克隆时,由于obj1中已经存在的属性将被简单地覆盖,因此它们在克隆中的顺序将被保留。


用于比较对象、数组、字符串、int等所有内容的最简单和逻辑的解决方案…

JSON.stringify({a: val1}) === JSON.stringify({a: val2})

注:

  • 你需要用你的对象替换val1val2
  • 对于对象,您必须对两侧的对象进行递归排序(按键)


你想测试两个物体是否相等吗?他们的性质是平等的?

如果是这种情况,您可能会注意到这种情况:

1
2
3
4
var a = { foo :"bar" };
var b = { foo :"bar" };
alert (a == b ?"Equal" :"Not equal");
//"Not equal"

您可能需要这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function objectEquals(obj1, obj2) {
    for (var i in obj1) {
        if (obj1.hasOwnProperty(i)) {
            if (!obj2.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    for (var i in obj2) {
        if (obj2.hasOwnProperty(i)) {
            if (!obj1.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    return true;
}

显然,该函数可以进行相当多的优化,并且能够进行深度检查(处理嵌套对象:var a = { foo : { fu :"bar" } }),但是您得到了这个想法。

正如所指出的,您可能需要为自己的目的而调整这个,例如:不同的类可能有不同的"相等"定义。如果您只是处理普通对象,那么上面的内容就足够了,否则定制的MyClass.equals()函数可能就是解决问题的方法。


在node.js中,可以使用其本机require("assert").deepEqual。更多信息:http://nodejs.org/api/assert.html网站

例如:

1
2
var assert = require("assert");
assert.deepEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError

返回truefalse而不是返回错误的另一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
var assert = require("assert");

function deepEqual(a, b) {
    try {
      assert.deepEqual(a, b);
    } catch (error) {
      if (error.name ==="AssertionError") {
        return false;
      }
      throw error;
    }
    return true;
};


我使用这个comparable函数生成我的对象的副本,这些对象与json类似:

1
2
3
4
5
6
7
8
9
10
11
var comparable = o => (typeof o != 'object' || !o)? o :
  Object.keys(o).sort().reduce((c, key) => (c[key] = comparable(o[key]), c), {});

// Demo:

var a = { a: 1, c: 4, b: [2, 3], d: { e: '5', f: null } };
var b = { b: [2, 3], c: 4, d: { f: null, e: '5' }, a: 1 };

console.log(JSON.stringify(comparable(a)));
console.log(JSON.stringify(comparable(b)));
console.log(JSON.stringify(comparable(a)) == JSON.stringify(comparable(b)));
1
 

在测试中非常方便(大多数测试框架都具有is功能)。例如。

1
is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');

如果捕捉到差异,则记录字符串,使差异可识别:

1
2
3
x must match y
got      {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.


下面是ES6/ES2015中使用功能风格方法的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const typeOf = x =>
  ({}).toString
      .call(x)
      .match(/\[object (\w+)\]/)[1]

function areSimilar(a, b) {
  const everyKey = f => Object.keys(a).every(f)

  switch(typeOf(a)) {
    case 'Array':
      return a.length === b.length &&
        everyKey(k => areSimilar(a.sort()[k], b.sort()[k]));
    case 'Object':
      return Object.keys(a).length === Object.keys(b).length &&
        everyKey(k => areSimilar(a[k], b[k]));
    default:
      return a === b;
  }
}

此处提供演示


我不知道是否有人发布了类似的内容,但这里有一个我用来检查对象均等性的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function objectsAreEqual(a, b) {
  for (var prop in a) {
    if (a.hasOwnProperty(prop)) {
      if (b.hasOwnProperty(prop)) {
        if (typeof a[prop] === 'object') {
          if (!objectsAreEqual(a[prop], b[prop])) return false;
        } else {
          if (a[prop] !== b[prop]) return false;
        }
      } else {
        return false;
      }
    }
  }
  return true;
}

而且,它是递归的,所以它也可以检查深度相等性,如果这就是你所说的。


您可以使用underline.js库中的_.isEqual(obj1, obj2)

下面是一个例子:

1
2
3
4
5
6
var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true

请参阅以下官方文档:http://underlinejs.org/isequal


对于这个问题,许多人没有意识到的一个简单的解决方案是对JSON字符串(每个字符)进行排序。这通常比这里提到的其他解决方案更快:

1
2
3
4
5
6
function areEqual(obj1, obj2) {
    var a = JSON.stringify(obj1), b = JSON.stringify(obj2);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}

这个方法的另一个有用之处是,通过将"replacer"函数传递给json.stringify函数(https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/json/stringify_example_of_using_replacer_参数),可以过滤比较。以下仅比较所有名为"derp"的对象键:

1
2
3
4
5
6
7
8
9
function areEqual(obj1, obj2, filter) {
    var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
    return (key === 'derp') ? value : undefined;
});


如果您通过babel或其他方式使用es6+,也可以使用Object.is(x, y)

参考:http://wiki.ecmascript.org/doku.php?id=harmony:egal object.is_x_y


只是想利用一些ES6特性来贡献我的对象比较版本。它不考虑订单。在将所有if/else转换为三元后,我得出了以下结论:

1
2
3
4
5
6
7
8
9
10
11
12
13
function areEqual(obj1, obj2) {

    return Object.keys(obj1).every(key => {

            return obj2.hasOwnProperty(key) ?
                typeof obj1[key] === 'object' ?
                    areEqual(obj1[key], obj2[key]) :
                obj1[key] === obj2[key] :
                false;

        }
    )
}

我也面临同样的问题,决定写自己的解决方案。但因为我还想将数组与对象进行比较,反之亦然,所以我设计了一个通用的解决方案。我决定将这些函数添加到原型中,但是可以很容易地将它们重写为独立的函数。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Array.prototype.equals = Object.prototype.equals = function(b) {
    var ar = JSON.parse(JSON.stringify(b));
    var err = false;
    for(var key in this) {
        if(this.hasOwnProperty(key)) {
            var found = ar.find(this[key]);
            if(found > -1) {
                if(Object.prototype.toString.call(ar) ==="[object Object]") {
                    delete ar[Object.keys(ar)[found]];
                }
                else {
                    ar.splice(found, 1);
                }
            }
            else {
                err = true;
                break;
            }
        }
    };
    if(Object.keys(ar).length > 0 || err) {
        return false;
    }
    return true;
}

Array.prototype.find = Object.prototype.find = function(v) {
    var f = -1;
    for(var i in this) {
        if(this.hasOwnProperty(i)) {
            if(Object.prototype.toString.call(this[i]) ==="[object Array]" || Object.prototype.toString.call(this[i]) ==="[object Object]") {
                if(this[i].equals(v)) {
                    f = (typeof(i) =="number") ? i : Object.keys(this).indexOf(i);
                }
            }
            else if(this[i] === v) {
                f = (typeof(i) =="number") ? i : Object.keys(this).indexOf(i);
            }
        }
    }
    return f;
}

该算法分为两部分:等号函数本身和查找数组/对象中属性的数字索引的函数。只需要find函数,因为indexof只查找数字和字符串,不查找对象。

人们可以这样称呼它:

1
({a: 1, b:"h"}).equals({a: 1, b:"h"});

在本例中,函数返回true或false。算法ALS允许比较非常复杂的对象:

1
({a: 1, b:"hello", c: ["w","o","r","l","d", {answer1:"should be", answer2: true}]}).equals({b:"hello", a: 1, c: ["w","d","o","r", {answer1:"should be", answer2: true},"l"]})

上面的示例将返回true,即使属性具有不同的顺序。需要注意的一个小细节:此代码还检查两个变量的相同类型,因此"3"与"3"不同。


我需要一个比发布的更通用的对象比较函数,我准备了以下内容。感谢批评…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.prototype.equals = function(iObj) {
  if (this.constructor !== iObj.constructor)
    return false;
  var aMemberCount = 0;
  for (var a in this) {
    if (!this.hasOwnProperty(a))
      continue;
    if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
      return false;
    ++aMemberCount;
  }
  for (var a in iObj)
    if (iObj.hasOwnProperty(a))
      --aMemberCount;
  return aMemberCount ? false : true;
}


如果要比较JSON对象,可以使用https://github.com/mirek/node-rus-diff

1
npm install rus-diff

用途:

1
2
3
4
5
6
7
8
a = {foo:{bar:1}}
b = {foo:{bar:1}}
c = {foo:{bar:2}}

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(a, b)) // -> false, meaning a and b are equal
console.log(rusDiff(a, c)) // -> { '$set': { 'foo.bar': 2 } }

如果两个对象不同,则返回与mongodb兼容的{$rename:{...}, $unset:{...}, $set:{...}}类对象。


我建议不要散列或序列化(正如JSON解决方案所建议的那样)。如果需要测试两个对象是否相等,那么需要定义相等的含义。可能是两个对象中的所有数据成员都匹配,也可能是内存位置必须匹配(意味着两个变量引用内存中的同一个对象),或者每个对象中只有一个数据成员必须匹配。

最近我开发了一个对象,它的构造函数在每次创建实例时都创建一个新的ID(从1开始,递增1)。此对象有一个ISequal函数,该函数将该ID值与另一个对象的ID值进行比较,如果匹配,则返回true。

在这种情况下,我定义"相等"是指ID值匹配。假设每个实例都有一个唯一的ID,那么可以使用它来强制实现这样一个想法:匹配的对象也占用相同的内存位置。尽管这不是必要的。


我知道这有点老,但我想添加一个解决这个问题的方案。我有一个对象,我想知道它的数据何时更改。"类似于物体的东西。观察"我所做的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function checkObjects(obj,obj2){
   var values = [];
   var keys = [];
   keys = Object.keys(obj);
   keys.forEach(function(key){
      values.push(key);
   });
   var values2 = [];
   var keys2 = [];
   keys2 = Object.keys(obj2);
   keys2.forEach(function(key){
      values2.push(key);
   });
   return (values == values2 && keys == keys2)
}

这里可以复制并创建另一组数组来比较值和键。这非常简单,因为它们现在是数组,如果对象大小不同,它们将返回false。


为了比较简单键/值对对象实例的键,我使用:

1
2
3
4
5
6
7
8
9
10
11
function compareKeys(r1, r2) {
    var nloops = 0, score = 0;
    for(k1 in r1) {
        for(k2 in r2) {
            nloops++;
            if(k1 == k2)
                score++;
        }
    }
    return nloops == (score * score);
};

一旦比较了键,一个简单的额外的for..in循环就足够了。

复杂性是O(n*n),n是键的数目。

我希望/猜测我定义的对象不会包含超过1000个属性…


如果两个对象的所有属性值都相同,并且所有嵌套对象和数组的递归值都相同,那么将它们视为相等是很有用的。我还认为以下两个对象相等:

1
2
var a = {p1: 1};
var b = {p1: 1, p2: undefined};

类似地,数组可以有"缺少"的元素和未定义的元素。我也会这样对待他们:

1
2
var c = [1, 2];
var d = [1, 2, undefined];

实现相等定义的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function isEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (generalType(a) != generalType(b)) {
        return false;
    }

    if (a == b) {
        return true;
    }

    if (typeof a != 'object') {
        return false;
    }

    // null != {}
    if (a instanceof Object != b instanceof Object) {
        return false;
    }

    if (a instanceof Date || b instanceof Date) {
        if (a instanceof Date != b instanceof Date ||
            a.getTime() != b.getTime()) {
            return false;
        }
    }

    var allKeys = [].concat(keys(a), keys(b));
    uniqueArray(allKeys);

    for (var i = 0; i < allKeys.length; i++) {
        var prop = allKeys[i];
        if (!isEqual(a[prop], b[prop])) {
            return false;
        }
    }
    return true;
}

源代码(包括helper函数、generaltype和uniquearray):这里是单元测试和测试运行程序。


这是对上述所有内容的补充,而不是替代。如果需要快速浅层比较对象,则无需检查额外的递归情况。这是一个镜头。

这比较了:1)自己属性的数目相等,2)键名相等,3)如果bcompareValues==true,则相应属性值及其类型相等(三重相等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var shallowCompareObjects = function(o1, o2, bCompareValues) {
    var s,
        n1 = 0,
        n2 = 0,
        b  = true;

    for (s in o1) { n1 ++; }
    for (s in o2) {
        if (!o1.hasOwnProperty(s)) {
            b = false;
            break;
        }
        if (bCompareValues && o1[s] !== o2[s]) {
            b = false;
            break;
        }
        n2 ++;
    }
    return b && n1 == n2;
}

我对这个函数做了以下假设:

  • 您可以控制正在比较的对象,并且只有基元值(即,没有嵌套的对象、函数等)。
  • 您的浏览器支持object.keys。
  • 这应该被视为一个简单策略的示范。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /**
     * Checks the equality of two objects that contain primitive values. (ie. no nested objects, functions, etc.)
     * @param {Object} object1
     * @param {Object} object2
     * @param {Boolean} [order_matters] Affects the return value of unordered objects. (ex. {a:1, b:2} and {b:2, a:1}).
     * @returns {Boolean}
     */

    function isEqual( object1, object2, order_matters ) {
        var keys1 = Object.keys(object1),
            keys2 = Object.keys(object2),
            i, key;

        // Test 1: Same number of elements
        if( keys1.length != keys2.length ) {
            return false;
        }

        // If order doesn't matter isEqual({a:2, b:1}, {b:1, a:2}) should return true.
        // keys1 = Object.keys({a:2, b:1}) = ["a","b"];
        // keys2 = Object.keys({b:1, a:2}) = ["b","a"];
        // This is why we are sorting keys1 and keys2.
        if( !order_matters ) {
            keys1.sort();
            keys2.sort();
        }

        // Test 2: Same keys
        for( i = 0; i < keys1.length; i++ ) {
            if( keys1[i] != keys2[i] ) {
                return false;
            }
        }

        // Test 3: Values
        for( i = 0; i < keys1.length; i++ ) {
            key = keys1[i];
            if( object1[key] != object2[key] ) {
                return false;
            }
        }

        return true;
    }

    对于那些使用nodejs的用户,在本机的Util库中有一个称为isDeepStrictEqual的方便方法可以实现这一点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const util = require('util');

    const foo = {
      hey:"ho",
      lets:"go"
    }

    const bar = {
      hey:"ho",
      lets:"go"
    }

    foo == bar // false
    util.isDeepStrictEqual(foo, bar) // true

    https://nodejs.org/api/util.html util诳u-isdeepstrictequal_-val1_-val2


    从我的个人图书馆里出来,我经常用它来做我的工作。下面的函数是一个宽松的递归深度相等,它不检查

    • 阶级平等
    • 继承的值
    • 值严格相等

    我主要使用它来检查我是否对各种API实现得到相同的答复。其中可能出现实现差异(如字符串与数字)和其他空值。

    它的实现非常简单(如果去掉所有的注释)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    /** Recursively check if both objects are equal in value
    ***
    *** This function is designed to use multiple methods from most probable
    *** (and in most cases) valid, to the more regid and complex method.
    ***
    *** One of the main principles behind the various check is that while
    *** some of the simpler checks such as == or JSON may cause false negatives,
    *** they do not cause false positives. As such they can be safely run first.
    ***
    *** # !Important Note:
    *** as this function is designed for simplified deep equal checks it is not designed
    *** for the following
    ***
    *** - Class equality, (ClassA().a = 1) maybe valid to (ClassB().b = 1)
    *** - Inherited values, this actually ignores them
    *** - Values being strictly equal,"1" is equal to 1 (see the basic equality check on this)
    *** - Performance across all cases. This is designed for high performance on the
    ***   most probable cases of == / JSON equality. Consider bench testing, if you have
    ***   more 'complex' requirments
    ***
    *** @param  objA : First object to compare
    *** @param  objB : 2nd object to compare
    *** @param  .... : Any other objects to compare
    ***
    *** @returns true if all equals, or false if invalid
    ***
    *** @license Copyright by [email protected], 2012.
    ***          Licensed under the MIT license: http://opensource.org/licenses/MIT
    **/

    function simpleRecusiveDeepEqual(objA, objB) {
        // Multiple comparision check
        //--------------------------------------------
        var args = Array.prototype.slice.call(arguments);
        if(args.length > 2) {
            for(var a=1; a<args.length; ++a) {
                if(!simpleRecusiveDeepEqual(args[a-1], args[a])) {
                    return false;
                }
            }
            return true;
        } else if(args.length < 2) {
            throw"simpleRecusiveDeepEqual, requires atleast 2 arguments";
        }
       
        // basic equality check,
        //--------------------------------------------
        // if this succed the 2 basic values is equal,
        // such as numbers and string.
        //
        // or its actually the same object pointer. Bam
        //
        // Note that if string and number strictly equal is required
        // change the equality from ==, to ===
        //
        if(objA == objB) {
            return true;
        }
       
        // If a value is a bsic type, and failed above. This fails
        var basicTypes = ["boolean","number","string"];
        if( basicTypes.indexOf(typeof objA) >= 0 || basicTypes.indexOf(typeof objB) >= 0 ) {
            return false;
        }
       
        // JSON equality check,
        //--------------------------------------------
        // this can fail, if the JSON stringify the objects in the wrong order
        // for example the following may fail, due to different string order:
        //
        // JSON.stringify( {a:1, b:2} ) == JSON.stringify( {b:2, a:1} )
        //
        if(JSON.stringify(objA) == JSON.stringify(objB)) {
            return true;
        }
       
        // Array equality check
        //--------------------------------------------
        // This is performed prior to iteration check,
        // Without this check the following would have been considered valid
        //
        // simpleRecusiveDeepEqual( { 0:1963 }, [1963] );
        //
        // Note that u may remove this segment if this is what is intended
        //
        if( Array.isArray(objA) ) {
            //objA is array, objB is not an array
            if( !Array.isArray(objB) ) {
                return false;
            }
        } else if( Array.isArray(objB) ) {
            //objA is not array, objB is an array
            return false;
        }
       
        // Nested values iteration
        //--------------------------------------------
        // Scan and iterate all the nested values, and check for non equal values recusively
        //
        // Note that this does not check against null equality, remove the various"!= null"
        // if this is required
       
        var i; //reuse var to iterate
       
        // Check objA values against objB
        for (i in objA) {
            //Protect against inherited properties
            if(objA.hasOwnProperty(i)) {
                if(objB.hasOwnProperty(i)) {
                    // Check if deep equal is valid
                    if(!simpleRecusiveDeepEqual( objA[i], objB[i] )) {
                        return false;
                    }
                } else if(objA[i] != null) {
                    //ignore null values in objA, that objB does not have
                    //else fails
                    return false;
                }
            }
        }
       
        // Check if objB has additional values, that objA do not, fail if so
        for (i in objB) {
            if(objB.hasOwnProperty(i)) {
                if(objB[i] != null && !objA.hasOwnProperty(i)) {
                    //ignore null values in objB, that objA does not have
                    //else fails
                    return false;
                }
            }
        }
       
        // End of all checks
        //--------------------------------------------
        // By reaching here, all iteration scans have been done.
        // and should have returned false if it failed
        return true;
    }

    // Sanity checking of simpleRecusiveDeepEqual
    (function() {
        if(
            // Basic checks
            !simpleRecusiveDeepEqual({}, {}) ||
            !simpleRecusiveDeepEqual([], []) ||
            !simpleRecusiveDeepEqual(['a'], ['a']) ||
            // Not strict checks
            !simpleRecusiveDeepEqual("1", 1) ||
            // Multiple objects check
            !simpleRecusiveDeepEqual( { a:[1,2] }, { a:[1,2] }, { a:[1,2] } ) ||
            // Ensure distinction between array and object (the following should fail)
            simpleRecusiveDeepEqual( [1963], { 0:1963 } ) ||
            // Null strict checks
            simpleRecusiveDeepEqual( 0, null ) ||
            simpleRecusiveDeepEqual("", null ) ||
            // Last"false" exists to make the various check above easy to comment in/out
            false
        ) {
            alert("FATAL ERROR: simpleRecusiveDeepEqual failed basic checks");
        } else {
            //added this last line, for SO snippet alert on success
            alert("simpleRecusiveDeepEqual: Passed all checks, Yays!");
        }
    })();


    我看到了意大利面条密码答案。不使用任何第三方libs,这很容易。

    首先按键对两个对象的键名进行排序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let objectOne = { hey, you }
    let objectTwo = { you, hey }

    // If you really wanted you could make this recursive for deep sort.
    const sortObjectByKeyname = (objectToSort) => {
        return Object.keys(objectToSort).sort().reduce((r, k) => (r[k] = objectToSort[k], r), {});
    }

    let objectOne = sortObjectByKeyname(objectOne)
    let objectTwo = sortObjectByKeyname(objectTwo)

    然后简单地使用一个字符串来比较它们。

    1
    JSON.stringify(objectOne) === JSON.stringify(objectTwo)


    这里有一个字符串化技巧的版本,它的类型更少,在很多情况下都可以用于简单的JSON数据比较。

    1
    2
    3
    var obj1Fingerprint = JSON.stringify(obj1).replace(/\{|\}/g,'').split(',').sort().join(',');
    var obj2Fingerprint = JSON.stringify(obj2).replace(/\{|\}/g,'').split(',').sort().join(',');
    if ( obj1Fingerprint === obj2Fingerprint) { ... } else { ... }

    取决于你所说的平等。因此,作为类的开发人员,您需要决定它们的相等性。

    有时会使用一种情况,如果两个实例指向内存中的同一位置,则认为它们"相等",但这并不总是您想要的。例如,如果我有一个Person类,如果两个Person对象具有相同的姓氏、名字和社会保险号(即使它们指向内存中的不同位置),我可能会认为它们"相等"。

    另一方面,我们不能简单地说,如果两个对象的每个成员的值相同,那么两个对象是相等的,因为有时您不希望这样。换句话说,对于每个类,由类开发人员定义组成对象"Identity"的成员,并开发适当的相等运算符(可以通过重载==运算符或equals方法实现)。

    如果两个对象具有相同的哈希值,那么说它们是相等的是一种解决方法。但是,您必须想知道如何为每个实例计算散列值。回到上面的Person示例,如果散列是通过查看first name、last name和social security number字段的值计算出来的,那么我们可以使用这个系统。除此之外,我们还依赖于散列方法的质量(这本身就是一个很大的主题,但足以说明并非所有散列的创建都是相等的,而错误的散列方法会导致更多的冲突,在本例中,这将返回错误匹配)。


    这里是检查对象"值相等"的一种非常基本的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    var john = {
        occupation:"Web Developer",
        age: 25
    };

    var bobby = {
        occupation:"Web Developer",
        age: 25
    };

    function isEquivalent(a, b) {
        // Create arrays of property names

        var aProps = Object.getOwnPropertyNames(a);
        var bProps = Object.getOwnPropertyNames(b);

        // If number of properties is different, objects are not equivalent

        if (aProps.length != bProps.length) {
            return false;
        }

        for (var i = 0; i < aProps.length; i++) {
            var propName = aProps[i];

            // If values of same property are not equal, objects are not equivalent
            if (a[propName] !== b[propName]) {
               return false;
            }
        }

        // If we made it this far, objects are considered equivalent
        return true;
    }

    // Outputs: true
    console.log(isEquivalent(john, bobby));

    演示- JSFiddle

    如您所见,为了检查对象的"值相等性",我们实际上必须对对象中的每个属性进行迭代,以查看它们是否相等。虽然这个简单的实现在我们的示例中有效,但是有很多情况它不能处理。例如:

    • 如果一个属性值本身是一个对象呢?
    • 如果属性值之一是NaN(不等于自身的javascript?)
    • 如果A有一个值未定义的属性,而B没有此属性(因此评估为未定义?)

    对于检查对象"值相等"的健壮方法,最好依赖于一个经过良好测试的库,该库覆盖各种边缘情况,如下划线。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var john = {
        occupation:"Web Developer",
        age: 25
    };

    var bobby = {
        occupation:"Web Developer",
        age: 25
    };

    // Outputs: true
    console.log(_.isEqual(john, bobby));

    演示- JSFiddle


    我不是javascript专家,但这里有一个简单的尝试来解决它。我检查了三件事:

  • 它是一个object,也不是null,因为typeof nullobject
  • 两个对象的属性计数是否相同?如果不是,它们就不相等。
  • 循环遍历一个的属性,并检查第二个对象中对应的属性是否具有相同的值。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    function deepEqual (first, second) {
      // Not equal if either is not an object or is null.
      if (!isObject(first) || !isObject(second) ) return false;

      // If properties count is different
      if (keys(first).length != keys(second).length) return false;

      // Return false if any property value is different.
      for(prop in first){
        if (first[prop] != second[prop]) return false;
      }
      return true;
    }

    // Checks if argument is an object and is not null
    function isObject(obj) {
      return (typeof obj ==="object" && obj != null);
    }

    // returns arrays of object keys
    function keys (obj) {
      result = [];
      for(var key in obj){
        result.push(key);
      }
      return result;
    }

    // Some test code
    obj1 = {
      name: 'Singh',
      age: 20
    }

    obj2 = {
      age: 20,
      name: 'Singh'
    }

    obj3 = {
      name: 'Kaur',
      age: 19
    }

    console.log(deepEqual(obj1, obj2));
    console.log(deepEqual(obj1, obj3));


    依靠。如果键在对象中的顺序不重要,我不需要知道该对象的原型。用总是做工作。

    1
    2
    const object = {};
    JSON.stringify(object) ==="{}" will pass but {} ==="{}" will not

    假设对象中属性的顺序没有更改。

    json.stringify()适用于深度和非深度两种类型的对象,对性能方面不太确定:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var object1 = {
      key:"value"
    };

    var object2 = {
      key:"value"
    };

    var object3 = {
      key:"no value"
    };

    console.log('object1 and object2 are equal: ', JSON.stringify(object1) === JSON.stringify(object2));

    console.log('object2 and object3 are equal: ', JSON.stringify(object2) === JSON.stringify(object3));


    对于这一个,有一个非常简单的修复方法,当您比较两个对象时,您所要做的就是在两个对象上执行json.stringify()。


    这是一个通用的相等检查函数,它接收元素数组作为输入,并相互比较。适用于所有类型的元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    const isEqual = function(inputs = []) {
      // Checks an element if js object.
      const isObject = function(data) {
        return Object.prototype.toString.call(data) === '[object Object]';
      };
      // Sorts given object by its keys.
      const sortObjectByKey = function(obj) {
        const self = this;
        if (!obj) return {};
        return Object.keys(obj).sort().reduce((initialVal, item) => {
          initialVal[item] = !Array.isArray(obj[item]) &&
            typeof obj[item] === 'object'
            ? self.objectByKey(obj[item])
            : obj[item];
          return initialVal;
        }, {});
      };

      // Checks equality of all elements in the input against each other. Returns true | false
      return (
        inputs
          .map(
            input =>
              typeof input == 'undefined'
                ? ''
                : isObject(input)
                    ? JSON.stringify(sortObjectByKey(input))
                    : JSON.stringify(input)
          )
          .reduce(
            (prevValue, input) =>
              prevValue === '' || prevValue === input ? input : false,
            ''
          ) !== false
      );
    };

    // Tests (Made with Jest test framework.)
    test('String equality check', () => {
      expect(isEqual(['murat'])).toEqual(true);
      expect(isEqual(['murat', 'john', 'doe'])).toEqual(false);
      expect(isEqual(['murat', 'murat', 'murat'])).toEqual(true);
    });

    test('Float equality check', () => {
      expect(isEqual([7.89, 3.45])).toEqual(false);
      expect(isEqual([7, 7.50])).toEqual(false);
      expect(isEqual([7.50, 7.50])).toEqual(true);
      expect(isEqual([7, 7])).toEqual(true);
      expect(isEqual([0.34, 0.33])).toEqual(false);
      expect(isEqual([0.33, 0.33])).toEqual(true);
    });

    test('Array equality check', () => {
      expect(isEqual([[1, 2, 3], [1, 2, 3]])).toEqual(true);
      expect(isEqual([[1, 3], [1, 2, 3]])).toEqual(false);
      expect(isEqual([['murat', 18], ['murat', 18]])).toEqual(true);
    });

    test('Object equality check', () => {
      let obj1 = {
        name: 'murat',
        age: 18
      };
      let obj2 = {
        name: 'murat',
        age: 18
      };
      let obj3 = {
        age: 18,
        name: 'murat'
      };
      let obj4 = {
        name: 'murat',
        age: 18,
        occupation: 'nothing'
      };
      expect(isEqual([obj1, obj2])).toEqual(true);
      expect(isEqual([obj1, obj2, obj3])).toEqual(true);
      expect(isEqual([obj1, obj2, obj3, obj4])).toEqual(false);
    });

    test('Weird equality checks', () => {
      expect(isEqual(['', {}])).toEqual(false);
      expect(isEqual([0, '0'])).toEqual(false);
    });

    还有一个要点


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
         let user1 = {
            name:"John",
            address: {
                line1:"55 Green Park Road",
                line2: {
                  a:[1,2,3]
                }
            },
            email:null
            }
       
         let user2 = {
            name:"John",
            address: {
                line1:"55 Green Park Road",
                line2: {
                  a:[1,2,3]
                }
            },
            email:null
             }
       
        // Method 1
       
        function isEqual(a, b) {
              return JSON.stringify(a) === JSON.stringify(b);
        }
       
        // Method 2
       
        function isEqual(a, b) {
          // checking type of a And b
          if(typeof a !== 'object' || typeof b !== 'object') {
            return false;
          }
         
          // Both are NULL
          if(!a && !b ) {
             return true;
          } else if(!a || !b) {
             return false;
          }
         
          let keysA = Object.keys(a);
          let keysB = Object.keys(b);
          if(keysA.length !== keysB.length) {
            return false;
          }
          for(let key in a) {
           if(!(key in b)) {
             return false;
           }
           
           if(typeof a[key] === 'object') {
             if(!isEqual(a[key], b[key]))
               {
                 return false;
               }
           } else {
             if(a[key] !== b[key]) {
               return false;
             }
           }
          }
         
          return true;
        }



    console.log(isEqual(user1,user2));


    我需要模拟jquery post请求,所以对我来说,重要的是两个对象都有相同的属性集(两个对象中都没有缺少属性集),并且每个属性值都是"相等的"(根据这个定义)。我不在乎有不匹配方法的对象。

    下面是我将要使用的,它应该足以满足我的特定需求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    function PostRequest() {
        for (var i = 0; i < arguments.length; i += 2) {
            this[arguments[i]] = arguments[i+1];
        }

        var compare = function(u, v) {
            if (typeof(u) != typeof(v)) {
                return false;
            }

            var allkeys = {};
            for (var i in u) {
                allkeys[i] = 1;
            }
            for (var i in v) {
                allkeys[i] = 1;
            }
            for (var i in allkeys) {
                if (u.hasOwnProperty(i) != v.hasOwnProperty(i)) {
                    if ((u.hasOwnProperty(i) && typeof(u[i]) == 'function') ||
                        (v.hasOwnProperty(i) && typeof(v[i]) == 'function')) {
                        continue;
                    } else {
                        return false;
                    }
                }
                if (typeof(u[i]) != typeof(v[i])) {
                    return false;
                }
                if (typeof(u[i]) == 'object') {
                    if (!compare(u[i], v[i])) {
                        return false;
                    }
                } else {
                    if (u[i] !== v[i]) {
                        return false;
                    }
                }
            }

            return true;
        };

        this.equals = function(o) {
            return compare(this, o);
        };

        return this;
    }

    使用如下:

    1
    2
    3
    4
    5
    6
    foo = new PostRequest('text', 'hello', 'html', '<p>
    hello
    </p>'
    );
    foo.equals({ html: '<p>
    hello
    </p>'
    , text: 'hello' });

    这里有很多好主意!这是我对深度相等的理解。我把它贴在Github上,并写了一些测试。很难涵盖所有可能的情况,有时也没有必要这样做。

    我介绍了NaN !== NaN和循环依赖关系。

    https://github.com/ryancat/simple-deep-equal/blob/master/index.js


    我实现了一个方法,它使用两个JSON,并使用递归检查它们的键是否具有相同的值。我用另一个问题来解决这个问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    const arraysEqual = (a, b) => {
        if (a === b)
            return true;
        if (a === null || b === null)
            return false;
        if (a.length !== b.length)
            return false;

        // If you don't care about the order of the elements inside
        // the array, you should sort both arrays here.

        for (let i = 0; i < a.length; ++i) {
            if (a[i] !== b[i])
                return false;
        }
        return true;
    };

    const jsonsEqual = (a, b) => {

      if(typeof a !== 'object' || typeof b !== 'object')
          return false;

      if (Object.keys(a).length === Object.keys(b).length) { // if items have the same size
          let response = true;

          for (let key in a) {
              if (!b[key]) // if not key
                  response = false;
              if (typeof a[key] !== typeof b[key]) // if typeof doesn't equals
                  response = false;
              else {
                  if (Array.isArray(a[key])) // if array
                      response = arraysEqual(a[key], b[key]);
                  else if (typeof a[key] === 'object') // if another json
                      response = jsonsEqual(a[key], b[key]);
                  else if (a[key] !== b[key])  // not equals
                      response = false;
              }
              if (!response) // return if one item isn't equal
                  return false;
          }
      } else
          return false;

      return true;
    };

    const json1 = {
      a: 'a',
      b: 'asd',
      c: [
        '1',
        2,
        2.5,
        '3',
        {
          d: 'asd',
          e: [
            1.6,
            {
              f: 'asdasd',
              g: '123'
            }
          ]
        }
      ],
      h: 1,
      i: 1.2,
    };

    const json2 = {
      a: 'nops',
      b: 'asd'
    };

    const json3 = {
      a: 'h',
      b: '484',
      c: [
        3,
        4.5,
        '2ss',
        {
          e: [
            {
              f: 'asdasd',
              g: '123'
            }
          ]
        }
      ],
      h: 1,
      i: 1.2,
    };

    const result = jsonsEqual(json1,json2);
    //const result = jsonsEqual(json1,json3);
    //const result = jsonsEqual(json1,json1);

    if(result) // is equal
      $('#result').text("Jsons are the same")
    else
      $('#result').text("Jsons aren't equals")
    1
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">


    我编写了一个运行在node.js和名为compare.js的浏览器上的小库。它提供了常见的比较运算符,如=,!=、>、>=、<、<=和所有javascript数据类型的标识。

    例如,您可以使用

    1
    cmp.eq(obj1, obj2);

    这将检查是否相等(使用深度相等的方法)。否则,如果你这样做了

    1
    cmp.id(obj1, obj2);

    它将通过引用进行比较,从而检查身份。您也可以在对象上使用<和>,这意味着子集和超集。

    compare.js包含了近700个单元测试,因此它应该没有太多的bug;-)。

    您可以在https://github.com/goloroden/compare.js上免费找到它,它是在MIT许可证下开放源代码的。


    当然,虽然我们在努力,我会投入我自己的车轮改造(我为轮辐的数量和使用的材料感到自豪):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    ////////////////////////////////////////////////////////////////////////////////

    var equals = function ( objectA, objectB ) {
        var result = false,
            keysA,
            keysB;

        // Check if they are pointing at the same variable. If they are, no need to test further.
        if ( objectA === objectB ) {
            return true;
        }

        // Check if they are the same type. If they are not, no need to test further.
        if ( typeof objectA !== typeof objectB ) {
            return false;
        }

        // Check what kind of variables they are to see what sort of comparison we should make.
        if ( typeof objectA ==="object" ) {
            // Check if they have the same constructor, so that we are comparing apples with apples.
            if ( objectA.constructor === objectA.constructor ) {
                // If we are working with Arrays...
                if ( objectA instanceof Array ) {
                    // Check the arrays are the same length. If not, they cannot be the same.
                    if ( objectA.length === objectB.length ) {
                        // Compare each element. They must be identical. If not, the comparison stops immediately and returns false.
                        return objectA.every(
                            function ( element, i ) {
                                return equals( element, objectB[ i ] );
                            }
                        );
                    }
                    // They are not the same length, and so are not identical.
                    else {
                        return false;
                    }
                }
                // If we are working with RegExps...
                else if ( objectA instanceof RegExp ) {
                    // Return the results of a string comparison of the expression.
                    return ( objectA.toString() === objectB.toString() );
                }
                // Else we are working with other types of objects...
                else {
                    // Get the keys as arrays from both objects. This uses Object.keys, so no old browsers here.
                    keysA = Object.keys( objectA );

                    keysB = Object.keys( objectB );

                    // Check the key arrays are the same length. If not, they cannot be the same.
                    if ( keysA.length === keysB.length ) {
                        // Compare each property. They must be identical. If not, the comparison stops immediately and returns false.
                        return keysA.every(
                            function ( element ) {
                                return equals( objectA[ element ], objectB[ element ] );
                            }
                        );
                    }
                    // They do not have the same number of keys, and so are not identical.
                    else {
                        return false;
                    }
                }
            }
            // They don't have the same constructor.
            else {
                return false;
            }
        }
        // If they are both functions, let us do a string comparison.
        else if ( typeof objectA ==="function" ) {
            return ( objectA.toString() === objectB.toString() );
        }
        // If a simple variable type, compare directly without coercion.
        else {
            return ( objectA === objectB );
        }

        // Return a default if nothing has already been returned.
        return result;
    };

    ////////////////////////////////////////////////////////////////////////////////

    它尽可能快地返回false,但对于深度嵌套差异的大型对象,效果可能会较差。在我自己的场景中,良好地处理嵌套数组非常重要。

    希望它能帮助需要这种"轮子"的人。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function isEqual(obj1, obj2){
        type1 = typeof(obj1);
        type2 = typeof(obj2);
        if(type1===type2){
            switch (type1){
                case"object": return JSON.stringify(obj1)===JSON.stringify(obj2);
                case"function": return eval(obj1).toString()===eval(obj2).toString();
                default: return obj1==obj2;
            }
        }
        return false;
    }//have not tried but should work.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    function isDeepEqual(obj1, obj2, testPrototypes = false) {
      if (obj1 === obj2) {
        return true
      }

      if (typeof obj1 ==="function" && typeof obj2 ==="function") {
        return obj1.toString() === obj2.toString()
      }

      if (obj1 instanceof Date && obj2 instanceof Date) {
        return obj1.getTime() === obj2.getTime()
      }

      if (
        Object.prototype.toString.call(obj1) !==
          Object.prototype.toString.call(obj2) ||
        typeof obj1 !=="object"
      ) {
        return false
      }

      const prototypesAreEqual = testPrototypes
        ? isDeepEqual(
            Object.getPrototypeOf(obj1),
            Object.getPrototypeOf(obj2),
            true
          )
        : true

      const obj1Props = Object.getOwnPropertyNames(obj1)
      const obj2Props = Object.getOwnPropertyNames(obj2)

      return (
        obj1Props.length === obj2Props.length &&
        prototypesAreEqual &&
        obj1Props.every(prop => isDeepEqual(obj1[prop], obj2[prop]))
      )
    }

    console.log(isDeepEqual({key: 'one'}, {key: 'first'}))
    console.log(isDeepEqual({key: 'one'}, {key: 'one'}))


    下面是一个非常干净的咖啡描述版,介绍了如何做到这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Object::equals = (other) ->
      typeOf = Object::toString

      return false if typeOf.call(this) isnt typeOf.call(other)
      return `this == other` unless typeOf.call(other) is '[object Object]' or
                                    typeOf.call(other) is '[object Array]'

      (return false unless this[key].equals other[key]) for key, value of this
      (return false if typeof this[key] is 'undefined') for key of other

      true

    以下是测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
      describe"equals", ->

        it"should consider two numbers to be equal", ->
          assert 5.equals(5)

        it"should consider two empty objects to be equal", ->
          assert {}.equals({})

        it"should consider two objects with one key to be equal", ->
          assert {a:"banana"}.equals {a:"banana"}

        it"should consider two objects with keys in different orders to be equal", ->
          assert {a:"banana", kendall:"garrus"}.equals {kendall:"garrus", a:"banana"}

        it"should consider two objects with nested objects to be equal", ->
          assert {a: {fruit:"banana"}}.equals {a: {fruit:"banana"}}

        it"should consider two objects with nested objects that are jumbled to be equal", ->
          assert {a: {a:"banana", kendall:"garrus"}}.equals {a: {kendall:"garrus", a:"banana"}}

        it"should consider two objects with arrays as values to be equal", ->
          assert {a: ["apple","banana"]}.equals {a: ["apple","banana"]}



        it"should not consider an object to be equal to null", ->
          assert !({a:"banana"}.equals null)

        it"should not consider two objects with different keys to be equal", ->
          assert !({a:"banana"}.equals {})

        it"should not consider two objects with different values to be equal", ->
          assert !({a:"banana"}.equals {a:"grapefruit"})


    以下一些解决方案在性能、功能和样式方面存在问题…他们思考得不够透彻,有些人在不同的情况下失败了。我试图用自己的解决方案来解决这个问题,非常感谢您的反馈:

    http://stamat.wordpress.com/javascript-object-comparison/

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    //Returns the object's class, Array, Date, RegExp, Object are of interest to us
    var getClass = function(val) {
        return Object.prototype.toString.call(val)
            .match(/^\[object\s(.*)\]$/)[1];
    };

    //Defines the type of the value, extended typeof
    var whatis = function(val) {

        if (val === undefined)
            return 'undefined';
        if (val === null)
            return 'null';

        var type = typeof val;

        if (type === 'object')
            type = getClass(val).toLowerCase();

        if (type === 'number') {
            if (val.toString().indexOf('.') > 0)
                return 'float';
            else
            return 'integer';
        }

        return type;
       };

    var compareObjects = function(a, b) {
        if (a === b)
            return true;
        for (var i in a) {
            if (b.hasOwnProperty(i)) {
                if (!equal(a[i],b[i])) return false;
            } else {
                return false;
            }
        }

        for (var i in b) {
            if (!a.hasOwnProperty(i)) {
                return false;
            }
        }
        return true;
    };

    var compareArrays = function(a, b) {
        if (a === b)
            return true;
        if (a.length !== b.length)
            return false;
        for (var i = 0; i < a.length; i++){
            if(!equal(a[i], b[i])) return false;
        };
        return true;
    };

    var _equal = {};
    _equal.array = compareArrays;
    _equal.object = compareObjects;
    _equal.date = function(a, b) {
        return a.getTime() === b.getTime();
    };
    _equal.regexp = function(a, b) {
        return a.toString() === b.toString();
    };
    //  uncoment to support function as string compare
    //  _equal.fucntion =  _equal.regexp;



    /*
     * Are two values equal, deep compare for objects and arrays.
     * @param a {any}
     * @param b {any}
     * @return {boolean} Are equal?
     */

    var equal = function(a, b) {
        if (a !== b) {
            var atype = whatis(a), btype = whatis(b);

            if (atype === btype)
                return _equal.hasOwnProperty(atype) ? _equal[atype](a, b) : a==b;

            return false;
        }

        return true;
    };

    这是我解决这个问题的方法。我不认为我的很好,但它适用于任何类型的对象比较

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Object.prototype.fullMatch = function(obj){
        if (typeof this !== typeof obj) return false;
        if (this == null && obj != null || this != null && obj == null) return false;
        var this_keys = [];
        var obj_keys = [];
        for (var key in this) if (this.hasOwnProperty(key)) this_keys.push(key);
        for (var key in obj) if (obj.hasOwnProperty(key)) obj_keys.push(key);
        if (this_keys.length !== obj_keys.length){
            this_keys = null;
            obj_keys = null;
            return false;
        }
        var full_match = true;
        for (var key in this){
            if (this.hasOwnProperty(key) && obj.hasOwnProperty(key)){
                var this_value = this[key];
                var obj_value = obj[key];
                if (typeof this_value !== typeof obj_value || ("object" === typeof this_value && !this_value.fullMatch(obj_value)) ||"object" !== typeof this_value && this_value !== obj_value){
                    full_match = false;
                    break;
                }
            }
        }
        return full_match;
    };

    我的版本,包括发现差异的链,以及差异是什么。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    function DeepObjectCompare(O1, O2)
    {
        try {
            DOC_Val(O1, O2, ['O1->O2', O1, O2]);
            return DOC_Val(O2, O1, ['O2->O1', O1, O2]);
        } catch(e) {
            console.log(e.Chain);
            throw(e);
        }
    }
    function DOC_Error(Reason, Chain, Val1, Val2)
    {
        this.Reason=Reason;
        this.Chain=Chain;
        this.Val1=Val1;
        this.Val2=Val2;
    }

    function DOC_Val(Val1, Val2, Chain)
    {
        function DoThrow(Reason, NewChain) { throw(new DOC_Error(Reason, NewChain!==undefined ? NewChain : Chain, Val1, Val2)); }

        if(typeof(Val1)!==typeof(Val2))
            return DoThrow('Type Mismatch');
        if(Val1===null || Val1===undefined)
            return Val1!==Val2 ? DoThrow('Null/undefined mismatch') : true;
        if(Val1.constructor!==Val2.constructor)
            return DoThrow('Constructor mismatch');
        switch(typeof(Val1))
        {
            case 'object':
                for(var m in Val1)
                {
                    if(!Val1.hasOwnProperty(m))
                        continue;
                    var CurChain=Chain.concat([m]);
                    if(!Val2.hasOwnProperty(m))
                        return DoThrow('Val2 missing property', CurChain);
                    DOC_Val(Val1[m], Val2[m], CurChain);
                }
                return true;
            case 'number':
                if(Number.isNaN(Val1))
                    return !Number.isNaN(Val2) ? DoThrow('NaN mismatch') : true;
            case 'string':
            case 'boolean':
                return Val1!==Val2 ? DoThrow('Value mismatch') : true;
            case 'function':
                if(Val1.prototype!==Val2.prototype)
                    return DoThrow('Prototype mismatch');
                if(Val1!==Val2)
                    return DoThrow('Function mismatch');
                return true;
            default:
                return DoThrow('Val1 is unknown type');
        }
    }

    我有一个更短的函数,它将深入到所有子对象或数组中。它与JSON.stringify(obj1) === JSON.stringify(obj2)一样有效,但如果订单不相同(如本文所述),JSON.stringify将不起作用。

    1
    2
    3
    4
    var obj1 = { a : 1, b : 2 };
    var obj2 = { b : 2, a : 1 };

    console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false

    如果你想用不相等的值做一些事情,这个函数也是一个好的开始。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    function arr_or_obj(v)
    { return !!v && (v.constructor === Object || v.constructor === Array); }

    function deep_equal(v1, v2)
    {
        if (arr_or_obj(v1) && arr_or_obj(v2) && v1.constructor === v2.constructor)
        {
            if (Object.keys(v1).length === Object.keys(v2).length) // check the length
            for (var i in v1)
            {
                if (!deep_equal(v1[i], v2[i]))
                { return false; }
            }
            else
            { return false; }
        }
        else if (v1 !== v2)
        { return false; }

        return true;
    }

    //////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////

    var obj1 = [
        {
            hat : {
                cap : ['something', null ],
                helmet : [ 'triple eight', 'pro-tec' ]
            },
            shoes : [ 'loafer', 'penny' ]
        },
        {
            beers : [ 'budweiser', 'busch' ],
            wines : [ 'barefoot', 'yellow tail' ]
        }
    ];

    var obj2 = [
        {
            shoes : [ 'loafer', 'penny' ], // same even if the order is different
            hat : {
                cap : ['something', null ],
                helmet : [ 'triple eight', 'pro-tec' ]
            }
        },
        {
            beers : [ 'budweiser', 'busch' ],
            wines : [ 'barefoot', 'yellow tail' ]
        }
    ];

    console.log(deep_equal(obj1, obj2)); // true
    console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // false
    console.log(deep_equal([], [])); // true
    console.log(deep_equal({}, {})); // true
    console.log(deep_equal([], {})); // false

    如果您想增加对FunctionDateRegExp的支持,可以在deep_equal的开头添加(未测试):

    1
    2
    3
    4
    5
    6
    7
    if ((typeof obj1 === 'function' && typeof obj2 === 'function') ||
    (obj1 instanceof Date && obj2 instanceof Date) ||
    (obj1 instanceof RegExp && obj2 instanceof RegExp))
    {
        obj1 = obj1.toString();
        obj2 = obj2.toString();
    }

    对象相等性检查:JSON.stringify(array1.sort()) === JSON.stringify(array2.sort())

    上面的测试也适用于对象数组,在这种情况下使用排序函数,如http://www.w3schools.com/jsref/jsref-sort.asp中所述。

    对于具有平面JSON模式的小数组来说可能已经足够了。


    是的,另一个答案…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Object.prototype.equals = function (object) {
        if (this.constructor !== object.constructor) return false;
        if (Object.keys(this).length !== Object.keys(object).length) return false;
        var obk;
        for (obk in object) {
            if (this[obk] !== object[obk])
                return false;
        }
        return true;
    }

    var aaa = JSON.parse('{"name":"mike","tel":"1324356584"}');
    var bbb = JSON.parse('{"tel":"1324356584","name":"mike"}');
    var ccc = JSON.parse('{"name":"mike","tel":"584"}');
    var ddd = JSON.parse('{"name":"mike","tel":"1324356584","work":"nope"}');

    $("#ab").text(aaa.equals(bbb));
    $("#ba").text(bbb.equals(aaa));
    $("#bc").text(bbb.equals(ccc));
    $("#ad").text(aaa.equals(ddd));
    1
    2
    3
    4
    5
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
    aaa equals bbb? <span id="ab"></span> <br/>
    bbb equals aaa? <span id="ba"></span> <br/>
    bbb equals ccc? <span id="bc"></span> <br/>
    aaa equals ddd? <span id="ad"></span>


    判断两个对象是否相似的快速"黑客"方法是使用它们的toString()方法。如果要检查对象A和B,请确保A和B具有有意义的toString()方法,并检查它们返回的字符串是否相同。

    这不是万灵药,但有时在正确的情况下也会有用。