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

我有两个对象:

1)

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

2)

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

正如你所看到的:除了一件事之外几乎相同:关键B值是不同的.

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

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

我正在使用角度,我的意思是这样的:

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))
// => ???

两个对象具有相同的属性. b属性不一样.只有x有一个c属性,只有y有一个d属性.那应该是什么???确切地说?

从diff的角度来看,输入对象a和b之间的关系可以完全是任意的.为了传达哪个对象有所贡献,diff左右分配描述符

console.log (diff (x,y))
// { b: { left: 2,right: 3 },c: { left: 3 },d: { right: 4 } }

在上面的输出中我们可以看到

>哪些属性不同 – b,c和d
>哪个对象有所不同 – 左和/或右
>“不同”值 – 例如左边的b值为2,右边b的值为3;或者左c的值为3,右c的值为undefined

在我们开始实现这个函数之前,我们首先要研究一个涉及深层嵌套对象的更复杂的场景

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,e: { left: 3 },f: { right: 4 } } } }

正如我们在上面看到的,diff返回一个与我们的输入匹配的结构.最后,我们期望两个相同的对象的差异返回“空”结果

const x1 =
  { a: 1,b: { c: { d: 2 } } }

const x2 =
  { a: 1,b: { c: { d: 2 } } }

console.log (diff (x1,x2))
// {}

上面我们描述了一个diff函数,它不关心它给出的输入对象. “left”对象可以包含“right”对象不包含的键,反之亦然,但我们仍然必须检测来自任何一方的更改.从高层开始,这就是我们如何处理这个问题

const diff = (x = {},y = {}) =>
  merge
    ( diff1 (x,y,"left"),diff1 (y,x,"right")
    )

DIFF1

我们使用描述为“左”关系的diff1进行“单侧”差异,并且我们采用另一个单侧差异,将输入对象反转描述为“右”关系,然后我们将两个结果合并在一起

我们的工作在我们现在更容易完成的任务中分工. diff1只需要检测一半必要的变化,合并只需合并结果.我们将从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,d: 4 }

console.log (diff1 (x,"left"))
// { b: { left: 2 },c: { left: 3 } }

console.log (diff1 (y,"right"))
// { b: { right: 3 },d: { right: 4 } }

另外值得注意的是关系标签“left”和“right”是用户可定义的.例如,如果您正在比较的对象之间存在已知关系,并且您希望在diff输出中提供更多描述性标签

const customDiff = (original = {},modified = {}) =>
  merge
    ( diff1 (x,"original"),"modified")
    )

customDiff
    ( { host: "localhost",port: 80 },{ host: "127.0.0.1",port: 80 }
    )
// { host: { original: 'localhost',modified: '127.0.0.1' } }

在上面的示例中,在程序的其他区域中使用输出可能更容易,因为原始和修改标签比左右更具描述性.

合并

剩下的就是将两个半差异合并成一个完整的结果.我们的合并函数也可以一般地工作,并接受任何两个对象作为输入.

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

const y =
  { b: 2,d: 2 }

console.log (merge (x,y))
// { a: 1,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,f: 2 }

下面我们编码合并的意图

const merge = (left = {},right = {}) =>
  Object.entries (right)
    .reduce
      ( (acc,v ]) =>
          isObject (v) && isObject (left [k])
            ? { ...acc,[k]: merge (left [k],v) }
            : { ...acc,left
      )

这就是整个套件和架子!展开下面的代码段,在您自己的浏览器中运行代码演示

const empty =
  {}

const isObject = x =>
  Object (x) === x

const diff1 = (left = {},empty
      )

const merge = (left = {},left
      )

const diff = (x = {},"right")
    )

const x =
  { a: { b: { c: 1,f: { right: 4 } } } }

console.log (diff (diff (x,y),diff (x,y)))
// {}

备注

回顾我们的diff函数,我想强调其设计的一个重要部分.很大一部分工作是由merge函数处理的,它与diff完全分开,但它本身就是tough nut to crack.因为我们将我们的关注点分解为单个函数,所以现在很容易在程序的其他区域重用它们.在我们想要差异的地方,我们得到了它,我们免费获得了直观的深度合并功能.

extra:支持数组

我们的diff函数非常方便,因为它可以抓取深层嵌套的对象,但是如果我们的一个对象属性一个数组呢?如果我们可以使用相同的技术来区分数组,那就太好了.

支持功能需要对上面的代码进行重要的更改.但是,大多数结构和推理保持不变.例如,diff完全不变

// unchanged
const diff = (x = {},"right")
    )

为了支持合并中的数组,我们引入了一个变异辅助mut,它将[key,value]对分配给给定的对象o.数组也被认为是对象,因此我们可以使用相同的mut函数更新数组和对象

const mut = (o,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 =
  [ 0,0 ]

const z =
  [,6 ]

console.log (merge (x,y))
// [ 0,5 ]

console.log (merge (y,z))
// [ 0,<2 empty items>,z))
// [ 1,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 },{ e: 2 } ] }

diff1中的支持数组更具挑战性

const diff1 = (left = {},{} ]
      )
    .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 },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根据其输入类型小心地改变了它的行为,所以我们可以免费获得数组diffing

const x =
  [ 1,4 ]

const y =
  [ 1,9 ]

const z =
  [ 1,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,o)

const diff1 = (left = {},isArray (left) && isArray (right) ? [] : {}
      )

const merge = (left = {},left)


const diff = (x = {},"right")
    )

const x =
  { a: 1,z: { right: 2 } 
// }

浅滩

这个答案的previous version提供了一个对象差异函数,用于比较具有相同键的对象和比较具有不同键的对象,但是这两个解决方案都没有在嵌套对象上递归地执行差异.

递归联盟

this related Q&A中,我们采用两个输入对象并计算递归联合而不是diff

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...