关于javascript:对象/数组的深度比较

Deep comparison of objects/arrays

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
How do you determine equality for two JavaScript objects?
Object comparison in JavaScript

如果我有两个数组或对象并想比较它们,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object1 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object2 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object1 == object2 // false

如果您收到服务器的响应并试图查看它是否发生了更改,这可能会很烦人。


更新:为了响应原始建议周围的评论和担忧(比较2个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
function compareObjects(o, p)
{
    var i,
        keysO = Object.keys(o).sort(),
        keysP = Object.keys(p).sort();
    if (keysO.length !== keysP.length)
        return false;//not the same nr of keys
    if (keysO.join('') !== keysP.join(''))
        return false;//different keys
    for (i=0;i<keysO.length;++i)
    {
        if (o[keysO[i]] instanceof Array)
        {
            if (!(p[keysO[i]] instanceof Array))
                return false;
            //if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
            //would work, too, and perhaps is a better fit, still, this is easy, too
            if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
                return false;
        }
        else if (o[keysO[i]] instanceof Date)
        {
            if (!(p[keysO[i]] instanceof Date))
                return false;
            if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
                return false;
        }
        else if (o[keysO[i]] instanceof Function)
        {
            if (!(p[keysO[i]] instanceof Function))
                return false;
            //ignore functions, or check them regardless?
        }
        else if (o[keysO[i]] instanceof Object)
        {
            if (!(p[keysO[i]] instanceof Object))
                return false;
            if (o[keysO[i]] === o)
            {//self reference?
                if (p[keysO[i]] !== p)
                    return false;
            }
            else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
                return false;//WARNING: does not deal with circular refs other than ^^
        }
        if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
            return false;//not the same value
    }
    return true;
}

但在很多情况下,在我看来,这并不难:

1
JSON.stringify(object1) === JSON.stringify(object2);

如果字符串化对象相同,则它们的值相同。
为了完整起见:JSON只忽略函数(好吧,将它们一起删除)。它是用来表示数据,而不是功能。
试图比较仅包含函数的2个对象将导致true

1
2
3
4
JSON.stringify({foo: function(){return 1;}}) === JSON.stringify({foo: function(){ return -1;}});
//evaulutes to:
'{}' === '{}'
//is true, of course

对于对象/函数的深入比较,您必须使用libs或编写自己的函数,并克服JS对象都是引用的事实,因此在比较o1 === ob2时,只有当两个变量指向同一个对象时,才会返回true…

正如@A-J在评论中指出的那样:

1
JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1});

false,因为字符串化调用分别产生"{"a":1,"b":2}""{"b":2,"a":1}"。至于这是为什么,你需要了解铬的V8引擎的内部。我不是一个专家,不需要太多的细节,我可以归结为:

创建的每个对象,每次被修改,V8创建一个新的隐藏C++类(排序)。如果对象x有一个属性a,而另一个对象有相同的属性,这两个JS对象将引用一个继承自定义该属性a的共享隐藏类的隐藏类。如果两个对象都具有相同的基本属性,那么它们都将引用相同的隐藏类,并且JSON.stringify对两个对象的作用完全相同。这是一个给定的(如果你感兴趣的话,这里有更多关于V8内部的细节)。

然而,在A-J所指出的例子中,两个对象的字符串化是不同的。怎么会?简单地说,这些对象永远不会同时存在:

1
JSON.stringify({a: 1, b: 2})

这是一个函数调用,需要先将其解析为结果值,然后才能将其与右侧操作数进行比较。第二个对象文本还不在表中。对象被串化,并且外显式被解析为字符串常量。对象文本未在任何地方被引用,已标记为垃圾收集。之后,右侧操作数(JSON.stringify({b: 2, a: 1})表达式)得到相同的处理。

一切都很好,但也需要考虑的是,JS引擎现在比以前复杂得多。同样,我不是V8专家,但我认为A-J的代码片段正在进行大量优化是合理的,因为代码优化到:

1
"{"b":2,"a":1}" ==="{"a":1,"b":2}"

基本上省略了JSON.stringify调用,只需在正确的地方添加引号。毕竟,这样效率更高。


作为下划线混合:

在咖啡脚本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_.mixin deepEquals: (ar1, ar2) ->

    # typeofs should match
    return false unless (_.isArray(ar1) and _.isArray(ar2)) or (_.isObject(ar1) and _.isObject(ar2))

    #lengths should match
    return false if ar1.length != ar2.length

    still_matches = true

    _fail = -> still_matches = false

    _.each ar1, (prop1, n) =>

      prop2 = ar2[n]

      return if prop1 == prop2

      _fail() unless _.deepEquals prop1, prop2

    return still_matches

在javascript中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_.mixin({
  deepEquals: function(ar1, ar2) {
    var still_matches, _fail,
      _this = this;
    if (!((_.isArray(ar1) && _.isArray(ar2)) || (_.isObject(ar1) && _.isObject(ar2)))) {
      return false;
    }
    if (ar1.length !== ar2.length) {
      return false;
    }
    still_matches = true;
    _fail = function() {
      still_matches = false;
    };
    _.each(ar1, function(prop1, n) {
      var prop2;
      prop2 = ar2[n];
      if (prop1 !== prop2 && !_.deepEquals(prop1, prop2)) {
        _fail();
      }
    });
    return still_matches;
  }
});