javascript - 如何比较两个对象并获得它们差异的键值对?

标签 javascript arrays angularjs

我有两个对象:

1)

{A: 10, B: 20, C: 30}

2)
{A: 10, B: 22, C: 30}

如您所见:几乎相等,除了一件事:键 B值(value)不同。

我怎样才能进入我的 someNewArr键值差异对等?

喜欢 someNewArr :{B: 22} (我从第二个对象获取值)

我正在使用 angular,我的意思是这样的:
    var compareTwoObjects = function(initialObj, editedObj) {
        var resultArr = [];
        angular.forEach(initialObj, function(firstObjEl, firstObjInd) {
            angular.forEach(editedObj, function(secondObjEl, secondObjInd) {
                if (firstObjEl.key === secondObjEl.key && firstObjEl.value !== secondObjEl.value){
                    resultArr.push({firstObjEl.key: secondObjEl.value});
                }
            })
        });
    });

最佳答案

递归差异
将近 3 年后,我很高兴为这个问题提供一个全新的答案。
我们从两个不同的对象开始

const x =
  { a: 1, b: 2, c: 3 }

const y =
  { a: 1, b: 3, d: 4 }

console.log (diff (x, y))
// => ???
两个对象具有相同的 a属性(property)。 b属性不一样。只有 x有一个 c属性(property),只有 y有一个 d属性(property)。那么应该怎么做???究竟是?
diff的 Angular 来看,我们输入对象之间的关系ab可以是完全任意的。要传达哪个对象造成差异,diff分配描述符 leftright
console.log (diff (x, y))
// { b: { left: 2, right: 3 }, c: { left: 3 }, d: { right: 4 } }
在上面的输出中我们可以看到
  • 哪些属性不同 – b , c , 和 d
  • 哪个对象造成了差异 - left和/或 right
  • “不同”值 - 例如左边 b值为 2,右边 b值为 3;或左边 c值为 3,右边 c具有未定义的值

  • 在我们进入这个函数的实现之前,我们将首先检查一个涉及深度嵌套对象的更复杂的场景
    const x =
      { a: { b: { c: 1, d: 2, e: 3 } } }
    
    const y =
      { a: { b: { c: 1, d: 3, f: 4 } } }
    
    console.log (diff (x, y))
    // { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }
    
    正如我们在上面看到的,diff返回与我们的输入匹配的结构。最后我们期待 diff两个相同的对象返回“空”结果
    const x1 =
      { a: 1, b: { c: { d: 2 } } }
    
    const x2 =
      { a: 1, b: { c: { d: 2 } } }
    
    console.log (diff (x1, x2))
    // {}
    
    上面我们描述了一个 diff不关心它给出的输入对象的函数。 “左”对象可以包含“右”对象不包含的键,反之亦然,但我们仍然必须检测任一侧的变化。从高层开始,这就是我们解决问题的方式
    const diff = (x = {}, y = {}) =>
      merge
        ( diff1 (x, y, "left")
        , diff1 (y, x, "right")
        ) 
    
    diff1
    我们使用 diff1 进行“单边”差异化描述为“左”关系,我们取另一个单边差异,输入对象颠倒描述为“右”关系,然后我们merge两个结果一起
    我们的工作被分配给现在更容易完成的任务。 diff1只需要检测一半的必要变化和merge简单地组合结果。我们将从 diff1 开始
    const empty =
      {}
      
    const isObject = x =>
      Object (x) === x
      
    const diff1 = (left = {}, right = {}, rel = "left") =>
      Object.entries (left)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (right[k])
                ? [ k, diff1 (v, right[k], rel) ]
                : right[k] !== v
                  ? [ k, { [rel]: v } ]
                  : [ k, empty ]
          )
        .reduce
          ( (acc, [ k, v ]) =>
              v === empty
                ? acc
                : { ...acc, [k]: v }
          , empty
          )
    
    diff1接受两个输入对象和一个关系描述符,rel .此描述符默认为 "left"这是比较的默认“方向”。下面,请注意 diff1只提供我们需要的一半结果。在第二次调用 diff1 时反转参数提供另一半。
    const x =
      { a: 1, b: 2, c: 3 }
    
    const y =
      { a: 1, b: 3, d: 4 }
      
    console.log (diff1 (x, y, "left"))
    // { b: { left: 2 }, c: { left: 3 } }
    
    console.log (diff1 (y, x, "right"))
    // { b: { right: 3 }, d: { right: 4 } }
    
    另外值得注意的是关系标签"left""right"是用户可定义的。例如,如果您在比较的对象之间有一个已知的关系,并且您希望在 diff 输出中提供更多的描述性标签......
    const customDiff = (x = {}, y = {}) =>
      merge
        ( diff1 (x, y, "original")
        , diff1 (y, x, "modified")
        )
    
    customDiff
        ( { host: "localhost", port: 80 }
        , { host: "127.0.0.1", port: 80 }
        )
    // { host: { original: 'localhost', modified: '127.0.0.1' } }
    
    在上面的示例中,由于标签 original,在程序的其他区域处理输出可能更容易。和 modifiedleft 更具描述性和 right .
    合并
    剩下的就是将两个半差异合并为一个完整的结果。我们的 merge函数也可以通用并接受任何两个对象作为输入。
    const x =
      { a: 1, b: 1, c: 1 }
    
    const y =
      { b: 2, d: 2 }
    
    console.log (merge (x, y))
    // { a: 1, b: 2, c: 1, d: 2 }
    
    如果每个对象都包含一个属性,其值也是一个对象,merge也会重复并合并嵌套的对象。
    const x =
      { a: { b: { c: 1, d: 1 } } }
    
    const y =
      { a: { b: { c: 2, e: 2 } }, f: 2 }
    
    console.log (merge (x, y))
    // { a: { b: { c: 2, d: 1, e: 2 } }, f: 2 }
    
    下面我们将我们的意图编码在 merge
    const merge = (left = {}, right = {}) =>
      Object.entries (right)
        .reduce
          ( (acc, [ k, v ]) =>
              isObject (v) && isObject (left [k])
                ? { ...acc, [k]: merge (left [k], v) }
                : { ...acc, [k]: v }
          , left
          )
    
    这就是整个套件和一堆!展开下面的代码片段,在您自己的浏览器中运行代码演示

    const empty =
      {}
    
    const isObject = x =>
      Object (x) === x
    
    const diff1 = (left = {}, right = {}, rel = "left") =>
      Object.entries (left)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (right[k])
                ? [ k, diff1 (v, right[k], rel) ]
                : right[k] !== v
                  ? [ k, { [rel]: v } ]
                  : [ k, empty ]
          )
        .reduce
          ( (acc, [ k, v ]) =>
              v === empty
                ? acc
                : { ...acc, [k]: v }
          , empty
          )
    
    const merge = (left = {}, right = {}) =>
      Object.entries (right)
        .reduce
          ( (acc, [ k, v ]) =>
              isObject (v) && isObject (left [k])
                ? { ...acc, [k]: merge (left [k], v) }
                : { ...acc, [k]: v }
          , left
          )
    
    const diff = (x = {}, y = {}) =>
      merge
        ( diff1 (x, y, "left")
        , diff1 (y, x, "right")
        )
    
    const x =
      { a: { b: { c: 1, d: 2, e: 3 } } }
    
    const y =
      { a: { b: { c: 1, d: 3, f: 4 } } }
    
    console.log (diff (x, y))
    // { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }
    
    console.log (diff (diff (x,y), diff (x,y)))
    // {} 

    备注
    当我们回顾我们的 diff功能,我想强调其设计的一个重要部分。很大一部分工作由 merge 处理完全独立于 diff 的功能,还有一个 tough nut to crack靠自己。因为我们将我们的关注点分成了单个函数,所以现在很容易在程序的其他区域重用它们。我们想要的地方 diff ,我们明白了,我们得到了直观的深度 merge功能免费。

    额外:支持数组
    我们的 diff函数非常方便,因为它可以抓取嵌套很深的对象,但是如果我们的对象属性之一是数组呢?如果我们可以使用相同的技术来区分数组,那就太好了。
    支持此功能需要对上述代码进行重大更改。但是,大部分结构和推理保持不变。例如,diff完全没有变化
    // unchanged
    const diff = (x = {}, y = {}) =>
      merge
        ( diff1 (x, y, "left")
        , diff1 (y, x, "right")
        )
    
    支持 merge 中的数组,我们引入了一个突变助手 mut它分配了一个 [ key, value ]与给定对象配对,o .数组也被视为对象,因此我们可以使用相同的 mut 更新数组和对象。功能
    const mut = (o, [ k, v ]) =>
      (o [k] = v, o)
    
    const merge = (left = {}, right = {}) =>
      Object.entries (right)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (left [k])
                ? [ k, merge (left [k], v) ]
                : [ k, v ]
          )
        .reduce (mut, left)
    
    浅合并按预期工作
    const x =
      [ 1, 2, 3, 4, 5 ]
    
    const y =
      [ , , , , , 6 ]
    
    const z =
      [ 0, 0, 0 ]
    
    console.log (merge (x, y))
    // [ 1, 2, 3, 4, 5, 6 ]
    
    console.log (merge (y, z))
    // [ 0, 0, 0, <2 empty items>, 6 ]
    
    console.log (merge (x, z))
    // [ 0, 0, 0, 4, 5, 6 ]
    
    并且也深度合并
    const x =
      { a: [ { b: 1 }, { c: 1 } ] }
    
    const y =
      { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }
    
    console.log (merge (x, y))
    // { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }
    
    支持数组 diff1更具挑战性
    const diff1 = (left = {}, right = {}, rel = "left") =>
      Object.entries (left)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (right[k])
                ? [ k, diff1 (v, right[k], rel) ]
                : right[k] !== v
                  ? [ k, { [rel]: v } ]
                  : [ k, {} ]
          )
        .filter
          ( ([ k, v ]) =>
              Object.keys (v) .length !== 0
          )
        .reduce
          ( mut
          , isArray (left) && isArray (right) ? [] : {}
          )
    
    但是有了这些变化,我们现在可以深入比较包含数组的对象——甚至包含对象的数组!
    const x =
      { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }
    
    const y =
      { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }
    
    console.log (diff (x, y))
    // { b:
    //     [ { c: { left: 1, right: 2 } }
    //     , <1 empty item>
    //     , { left: { e: 1 }, right: 5 }
    //     , { right: 6 }
    //     ]
    // , z: { right: 2 } 
    // }
    
    因为 diff1根据输入类型仔细改变其行为,我们免费获得数组差异
    const x =
      [ 1, 2, 3, 4 ]
    
    const y =
      [ 1, 2, 9 ]
    
    const z =
      [ 1, 2, 9 ]
    
    console.log (diff (x, y))
    // [ <2 empty items>, { left: 3, right: 9 }, { left: 4 } ]
    
    console.log (diff (y, z))
    // []
    
    在下面的浏览器中运行完整程序

    const isObject = x =>
      Object (x) === x
    
    const isArray =
      Array.isArray
    
    const mut = (o, [ k, v ]) =>
      (o [k] = v, o)
    
    const diff1 = (left = {}, right = {}, rel = "left") =>
      Object.entries (left)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (right[k])
                ? [ k, diff1 (v, right[k], rel) ]
                : right[k] !== v
                  ? [ k, { [rel]: v } ]
                  : [ k, {} ]
          )
        .filter
          ( ([ k, v ]) =>
              Object.keys (v) .length !== 0
          )
        .reduce
          ( mut
          , isArray (left) && isArray (right) ? [] : {}
          )
    
    const merge = (left = {}, right = {}) =>
      Object.entries (right)
        .map
          ( ([ k, v ]) =>
              isObject (v) && isObject (left [k])
                ? [ k, merge (left [k], v) ]
                : [ k, v ]
          )
        .reduce (mut, left)
    
    
    const diff = (x = {}, y = {}) =>
      merge
        ( diff1 (x, y, "left")
        , diff1 (y, x, "right")
        )
    
    const x =
      { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }
    
    const y =
      { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }
    
    console.log (diff (x, y))
    // { b:
    //     [ { c: { left: 1, right: 2 } }
    //     , <1 empty item>
    //     , { left: { e: 1 }, right: 5 }
    //     , { right: 6 }
    //     ]
    // , z: { right: 2 } 
    // }

    浅差异
    previous version这个答案提供了一个对象 diff用于比较具有相同键的对象和比较具有不同键的对象的函数,但两种解决方案都没有对嵌套对象递归执行差异。
    递归交集
    this related Q&A ,我们取两个输入对象并计算递归 intersect而不是 diff .

    关于javascript - 如何比较两个对象并获得它们差异的键值对?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33232823/

    相关文章:

    javascript - AngularJS动画错误: no method 'classNameFilter'

    javascript - Angularjs 模型未在嵌套 ng-repeat 内更新

    Javascript 返回仅适用于某些网站

    javascript - 将客户端功能添加到表中

    javascript - jquery/javascript - 简单的 split() 问题

    java - 解释一下代码

    javascript:如果数组中的值在另一个数组中,则只删除一次该值

    ruby-on-rails - 如何通过数组将 'long'表转换为 'wide'表?

    php - AngularJs http 获取到 PHP 脚本返回 php 代码

    javascript - document.getElementById.style.backgroundImage 不工作