对象/数组的深度比较[重复]

Posted

技术标签:

【中文标题】对象/数组的深度比较[重复]【英文标题】:Deep comparison of objects/arrays [duplicate] 【发布时间】:2012-10-20 00:39:05 【问题描述】:

可能重复:How do you determine equality for two javascript objects?Object comparison in JavaScript

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

object1 = [
  shoes:
   [ 'loafer', 'penny' ]
  ,
   beers:
     [ 'budweiser', 'busch' ]
  
]

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

object1 == object2 // false

如果您从服务器获得响应并尝试查看它是否已更改,这可能会很烦人

【问题讨论】:

相关:***.com/questions/201183/…. 【参考方案1】:

更新: 为了回应围绕原始建议的 cmets 和担忧(比较 2 个 JSON 字符串),您可以使用此函数:

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;

但在许多情况下,IMO 不必那么困难:

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

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

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

对于对象/函数的深度比较,你必须求助于库或编写你自己的函数,并克服 JS 对象都是引用的事实,所以在比较 o1 === ob2 时,它只会在以下情况下返回 true两个变量都指向同一个对象...

正如@a-j 在评论中指出的那样:

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

false,因为两个字符串化调用分别产生""a":1,"b":2"""b":2,"a":1"。至于为什么会这样,你需要了解 chrome 的 V8 引擎的内部结构。我不是专家,没有过多的细节,这就是它归结为:

每个创建的对象,每次修改时,V8 都会创建一个新的隐藏 C++ 类(有点)。如果对象 X 具有属性a,而另一个对象具有相同的属性,则这两个 JS 对象都将引用一个隐藏类,该隐藏类继承自定义此属性 a 的共享隐藏类。如果两个对象都共享相同的基本属性,那么它们都将引用相同的隐藏类,JSON.stringify 在两个对象上的工作方式完全相同。这是给定的(有关 V8 内部的更多详细信息 here,如果您有兴趣)。

但是,在 a-j 指出的示例中,两个对象的字符串化方式不同。怎么会?好吧,简单地说,这些对象永远不会同时存在:

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

这是一个函数调用,一个表达式,需要先解析为结果值,然后才能与右侧的操作数进行比较。第二个对象字面量还没有放在桌面上。 对象被字符串化,并且外显被解析为字符串常量。对象字面量没有在任何地方被引用,并被标记为垃圾回收。 此后,右手操作数(JSON.stringify(b: 2, a: 1) 表达式)得到相同的处理。

一切都很好,但还需要考虑的是,现在的 JS 引擎比以前复杂得多。同样,我不是 V8 专家,但我认为 a-j 的 sn-p 被大量优化是合理的,因为代码被优化为:

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

基本上省略了JSON.stringify 调用,只是在正确的位置添加引号。毕竟,这样效率要高得多。

【讨论】:

这不一定是真的。这并不包括所有情况,即作为值的函数,作为值的对象——你最终可能会得到很多 [Object Object]——这可能是误报。 @ansiart:我从未声称这是一个通用的解决方案。 OP 想要比较两个对象,就像他的问题中的对象一样。要实现这一点,这个答案是最简单的方法。 我不相信 JSON.stringify 对输出的顺序有任何保证。 JSON.stringify(a: 1, b: 1) 可以变成 '"a": 1, "b": 1' 或 '"b": 1, "a": 1' 和比较会失败。 @pomo:不要认为这是故意的(这是一个 4.5 年前的答案)。可能忘记为数组元素递归调用它,或者可能与原始问题或某处的评论有关 @Michal:对于花时间批评一个 5 岁的 sn-p 代码的人来说,你无法弄清楚 return truereturn false 的含义是非常了不起的(是的,函数返回布尔值,true 等于,false 否则)。 op 显然是参数,考虑到函数被称为 compareObjects,它们可能是 objects(因此 o)。无论哪种方式:是的,名字不是很好,风格也不是......再说一遍:它已经 5 岁了......如果我还在做 JS,我会使用 ES6/ 以完全不同的方式编写它ES2016,也许将其命名为 objectEquals 或其他任何名称【参考方案2】:

作为下划线混合:

在咖啡脚本中:

_.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 中:

_.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;
  
);

【讨论】:

Funkodebat,你传给下划线的迭代器函数返回点不一致: if (prop1 === prop2) return; /* void */ if (!_.deepEquals(prop1, prop2)) return _fail(); /* 错误 */ 从迭代器返回的目的是什么?据我了解, _.each 不会打破循环(如果这是你想要的).. 或者它只是咖啡脚本的半自动转换,其中每个语句都有一个返回值,所以当调用“_fail() 除非 _.deepEquals prop1, prop2”时,它会将其转换为“return _fail ()" ? 是的,cofeescript 只返回函数的最后一行 下划线的_.isEqual()有区别吗?请参阅underscorejs.org/#isEqual 说它“在两个对象之间执行优化的深度比较,以确定它们是否应该被视为相等。”

以上是关于对象/数组的深度比较[重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何比较两个数组并通过完整迭代删除重复的对象

如何获取对象数组并减少它,以便组合重复对象键处的数据?

用数组深度复制一个类[重复]

基于对象属性对数组进行排序 - Javascript [重复]

合并排序“比较超过最大递归深度” [重复]

关于如何去除数组中重复项