【问题标题】:Generic deep diff between two objects两个对象之间的通用深度差异
【发布时间】:2012-01-24 06:17:00
【问题描述】:

我有两个对象:oldObjnewObj

oldObj 中的数据用于填充表单,newObj 是用户更改此表单中的数据并提交的结果。

两个对象都很深,即。它们具有对象或对象数组等属性 - 它们可以是 n 级深,因此 diff 算法需要是递归的。

现在我不仅需要弄清楚从 oldObjnewObj 的更改(如添加/更新/删除),还需要弄清楚如何最好地表示它。

到目前为止,我的想法是构建一个 genericDeepDiffBetweenObjects 方法,该方法将返回 {add:{...},upd:{...},del:{...}} 形式的对象,但后来我想:以前一定有人需要这个。

那么...有没有人知道一个库或一段代码可以做到这一点,并且可能有更好的方式来表示差异(以仍然是 JSON 可序列化的方式)?

更新:

我想到了一种更好的方式来表示更新的数据,使用与newObj相同的对象结构,但将所有属性值转换为表单上的对象:

{type: '<update|create|delete>', data: <propertyValue>}

所以如果newObj.prop1 = 'new value'oldObj.prop1 = 'old value' 它将设置returnObj.prop1 = {type: 'update', data: 'new value'}

更新 2:

当我们处理数组属性时,它变得非常棘手,因为数组[1,2,3] 应该被视为等于[2,3,1],这对于基于值的类型的数组(如字符串、int 和 bool)来说足够简单,但是当涉及到对象和数组等引用类型的数组时,真的很难处理。

应该找到相等的示例数组:

[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]

不仅检查这种类型的深度值相等非常复杂,而且找出一种表示可能发生的变化的好方法。

【问题讨论】:

  • @a'r:它不是 stackoverflow.com/questions/1200562/… 的副本 - 我知道如何遍历对象,我正在寻找现有技术,因为这不是微不足道的,需要实时实现,我宁愿使用图书馆也不愿从头开始。
  • 你真的需要对象的差异,是在表单提交响应中从服务器生成的 newObj 吗?因为如果您没有对象的“服务器更新”,您可以通过附加适当的事件侦听器和用户交互(对象更改)来简化您的问题,您可以更新/生成想要的更改列表。
  • @sbgoran: newObj 由 js 代码从 DOM 中的表单读取值生成。有几种方法可以保持状态并且更容易做到这一点,但我想保持无状态作为练习。此外,我正在寻找现有技术,看看其他人如何解决这个问题,如果确实有人这样做的话。
  • 这里有一个非常复杂的库,可以区分/修补任何一对 Javascript 对象 github.com/benjamine/jsondiffpatch 你可以在这里看到它:benjamine.github.io/jsondiffpatch/demo/index.html(免责声明:我是作者)

标签: javascript object compare


【解决方案1】:

我知道我迟到了,但我需要类似的东西,但上述答案没有帮助。

我使用 Angular 的 $watch 函数来检测变量的变化。我不仅需要知道变量上的属性是否已更改,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。

代码如下:

function diff(obj1,obj2,exclude) {
        var r = {};
    
    if (!exclude)   exclude = [];
    
    for (var prop in obj1) {
      if (obj1.hasOwnProperty(prop) && prop != '__proto__') {
            if (exclude.indexOf(obj1[prop]) == -1) {

            // check if obj2 has prop
            if (!obj2.hasOwnProperty(prop)) r[prop] = obj1[prop];

            // check if prop is object and 
            // NOT a JavaScript engine object (i.e. __proto__), if so, recursive diff
            else if (obj1[prop] === Object(obj1[prop])) {
              var difference = diff(obj1[prop], obj2[prop]);
              if (Object.keys(difference).length > 0) r[prop] = difference;
            }

            // check if obj1 and obj2 are equal
            else if (obj1[prop] !== obj2[prop]) {
                if (obj1[prop] === undefined)
                r[prop] = 'undefined';
                if (obj1[prop] === null)
                r[prop] = null;
              else if (typeof obj1[prop] === 'function')
                r[prop] = 'function';
              else if (typeof obj1[prop] === 'object')  
                r[prop] = 'object';
              else
                  r[prop] = obj1[prop];
            }
          }
       }
       
    }
    
    return r;
}

https://jsfiddle.net/rv01x6jo/


使用方法如下:

// To only return the difference
var difference = diff(newValue, oldValue);  

// To exclude certain properties
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);

希望这对某人有所帮助。

【讨论】:

  • 请在您的答案中也包含代码,而不仅仅是小提琴。
  • defineProperty 似乎会以更好的性能解决这个问题,如果我没记错的话,它可以一直运行到 IE9。
  • 谢谢..!!你的代码就像魅力一样工作,拯救了我的一天。我有 1250 行的 json 对象,它给了我想要的准确的 o/p。
【解决方案2】:

[1,2,3][3,2,1] 视为平等(深层对象)
因为我需要可视化以下之间的区别:

[
  {
    "a":1,
    "b":1
  },
  {
    "a":1,
    "b":1
  }
]

[
  {
    "a":1,
    "b":1
  },
  {
    "a":"OH NO",
    "b":"an insertion"
  },
  {
    "a":1,
    "b":1
  }
]

所以我想看到它们碰撞,这是剩下的:

[]
and
[
  {
    "a":"OH NO",
    "b":"an insertion"
  }
]

imo 这是代表​​它的最佳方式。 {add:{...},upd:{...},del:{...}} 难以阅读


我提供 2 个函数:ObjectCollide(obj1,obj2)ArrayCollide(arr1,arr2)

console.log(ArrayCollide([1,2,3],[3,2,1]))
// false
//everything collided -> false
console.log(ArrayCollide([1],[2,1]))
// [ [], [ 2 ] ]
//1 and 1 collided, even if they are on different indices

//array of objects
const arr1 = 
[
  {
    "a":1,
    "b":1
  },
  {
    "a":1,
    "b":1
  }
]
const arr2 = 
[
  {
    "a":1,
    "b":1
  },
  {
    "a":"OH NO",
    "b":"an insertion"
  },
  {
    "a":1,
    "b":1
  }
]
const newArrays = ArrayCollide(arr1, arr2)
console.log(newArrays[0])
console.log(newArrays[1])
console.log('\n')
// []
// [ { a: 'OH NO', b: 'an insertion' } ]
// everything collided until this is left

//ObjectCollide
const obj1 = { a: '111', c: { q: 'no', a: '333' } }
const obj2 = { a: '111', p: 'ok', c: { a: '333' } }
ObjectCollide(obj1, obj2) //in place
console.log(obj1)
console.log(obj2)
console.log('\n')
// { c: { q: 'no' } }
// { p: 'ok', c: {} }
// obj["a"] collided and obj["c"]["a"] collided

//testing empty array
const a1 = { a: [] }
const a2 = { a: [], b: '2' }
ObjectCollide(a1, a2) //in place
console.log(a1)
console.log(a2)
console.log('\n')
// {}
// { b: '2' }
// obj["a"] collided

//DIFFERENT TYPES
const b1 = {a:true}
const b2 = {a:[1,2]}
ObjectCollide(b1,b2) //in place
console.log(b1)
console.log(b2)
// { a: true }
// { a: [ 1, 2 ] }

function ObjectCollide(obj1, obj2) {
  //in place, returns true if same

  // delete same
  const keys = Object.keys(obj1)
  const len = keys.length
  let howManyDeleted = 0
  for (let i = 0; i < len; i++) {
    const key = keys[i]

    const type1 = Array.isArray(obj1[key]) === true ? 'array' : typeof obj1[key]
    const type2 = Array.isArray(obj2[key]) === true ? 'array' : typeof obj2[key]
    if (type1!==type2) {
      continue
    }
    switch (type1) {
      case 'object':
        if (ObjectCollide(obj1[key], obj2[key])) {
          delete obj1[key]
          delete obj2[key]
          howManyDeleted++
        }
        continue
      case 'array':
        const newArrays = ArrayCollide(obj1[key], obj2[key])
        if (newArrays) {
          obj1[key] = newArrays[0]
          obj2[key] = newArrays[1]
        } else {
          delete obj1[key]
          delete obj2[key]
          howManyDeleted++
        }
        continue
      default:
        //string, number, I hope it covers everything else
        if (obj1[key] === obj2[key]) {
          delete obj1[key]
          delete obj2[key]
          howManyDeleted++
        }
    }
  }


  if (howManyDeleted === len && Object.keys(obj2).length === 0) {
    // return 'delete the stuff'
    // same. we've deleted everything!
    return true
  }

}
function ArrayCollide(arr1, arr2) {
  // returns [newArr1, newArr2] or false if same arrays (ignore order)
  const stringifyObj = {}

  const newArr1 = []
  const newArr2 = []
  for (let i = 0, len = arr1.length; i < len; i++) {
    const value = arr1[i]
    const stringified = JSON.stringify(value)
    stringifyObj[stringified]
    // arr = [count, ...]
    const arr = stringifyObj[stringified] || (stringifyObj[stringified] = [0])
    arr[0]++
    arr.push(value)
  }
  //in 2 but not in 1
  for (let i = 0, len = arr2.length; i < len; i++) {
    const value = arr2[i]
    const stringified = JSON.stringify(value)
    const arr = stringifyObj[stringified]
    if (arr === undefined) {
      newArr2.push(value)
    } else {
      if (arr[0] === 0) {
        newArr2.push(value)
      } else {
        arr[0]--
      }
    }
  }
  //in 1 but not in 2
  stringifyKeys = Object.keys(stringifyObj)
  for (let i = 0, len = stringifyKeys.length; i < len; i++) {
    const arr = stringifyObj[stringifyKeys[i]]

    for (let i = 1, len = arr[0] + 1; i < len; i++) {
      newArr1.push(arr[i])
    }
  }
  if (newArr1.length || newArr2.length) {
    return [newArr1, newArr2]
  } else {
    return false
  }

}

我想解决的问题:

JSON 文件不断重新排序,如果它是等效的,我想恢复 JSON:比如 {a:1,b:2}{b:2,a:1}
但是因为我不信任我的代码(我犯了一次错误),我想查看 diff 并自己检查,我可以使用这个 diff Ctrl+F 进入原始文件。

【讨论】:

    【解决方案3】:

    另一个基于 lodash 的解决方案有点特定于我们想要查看对对象进行更新的差异的情况:

    const diff = return {
      old: _.pickBy(oldObject, (value, key) => { return !_.isEqual(value, newObject[key]); }),
      new: _.pickBy(newObject, (value, key) => { return !_.isEqual(oldObject[key], value); })
    }
    

    由于性能影响,未使用 _.omitBy

    【讨论】:

    • 漂亮而简单。谢谢。
    【解决方案4】:

    这仅返回具有更改属性的新对象。 (omit 和 isEmpty 是 lodash 的函数)

    export const getObjectDifference = <T extends {}>(originalObject: T, newObject: T) => {
    const sameProperties: string[] = [];
    
    Object.entries(originalObject).forEach(original => {
        Object.entries(newObject).forEach(newObj => {
            if (original[0] === newObj[0]) {
                if (original[1] === newObj[1])
                    sameProperties.push(newObj[0]);
            }
        });
    });
    
    const objectDifference: T = omit(newObject, sameProperties) as T;
    if (isEmpty(objectDifference))
        return null;
    else
        return objectDifference; }
    

    【讨论】:

      【解决方案5】:

      这是一个 JavaScript 库,可用于查找两个 JavaScript 对象之间的差异:

      Github 网址: https://github.com/cosmicanant/recursive-diff

      Npmjs 网址: https://www.npmjs.com/package/recursive-diff

      recursive-diff 库可用于浏览器以及基于 Node.js 的服务器端应用程序。对于浏览器,可以如下使用:

      <script type="text" src="https://unpkg.com/recursive-diff@latest/dist/recursive-diff.min.js"/>
      <script type="text/javascript">
           const ob1 = {a:1, b: [2,3]};
           const ob2 = {a:2, b: [3,3,1]};
           const delta = recursiveDiff.getDiff(ob1,ob2); 
           /* console.log(delta) will dump following data 
           [
               {path: ['a'], op: 'update', val: 2}
               {path: ['b', '0'], op: 'update',val: 3},
               {path: ['b',2], op: 'add', val: 1 },
           ]
            */
           const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
       </script>
      

      而在基于 node.js 的应用程序中,它可以如下使用:

      const diff = require('recursive-diff');
      const ob1 = {a: 1}, ob2: {b:2};
      const diff = diff.getDiff(ob1, ob2);
      

      【讨论】:

      • 这不会考虑日期属性的变化,例如。
      • 添加日期支持
      • 这似乎无法识别移动的内容。例如。正确地将{ a: { b: { c: '...' }}}{ b: { c: '...' }, a: {}} 之间的差异识别为“将/a/b 移动到/b
      • 您可能认为移动是删除然后插入的组合。您可以编写自己的算法来从计算的差异中获取此数据。恕我直言,差异算法在计算差异时很简单,同时考虑到对象/数组的保留顺序,否则它可能会更复杂。数组插入和删除也有同样的问题,单个插入/删除可以使整个差异数据膨胀。
      【解决方案6】:

      我为自己的用例(es5 环境)编写了这个,认为这可能对某人有用,所以这里是:

      function deepCompare(obj1, obj2) {
          var diffObj = Array.isArray(obj2) ? [] : {}
          Object.getOwnPropertyNames(obj2).forEach(function(prop) {
              if (typeof obj2[prop] === 'object') {
                  diffObj[prop] = deepCompare(obj1[prop], obj2[prop])
                  // if it's an array with only length property => empty array => delete
                  // or if it's an object with no own properties => delete
                  if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
                      delete diffObj[prop]
                  }
              } else if(obj1[prop] !== obj2[prop]) {
                  diffObj[prop] = obj2[prop]
              }
          });
          return diffObj
      }
      

      这可能效率不高,但会根据第二个 Obj 输出一个只有不同 props 的对象。

      【讨论】:

        【解决方案7】:

        我写了一个小类,做你想做的,你可以测试一下here

        唯一与你的提议不同的是我不考虑

        [1,[{c: 1},2,3],{a:'hey'}]
        

        [{a:'hey'},1,[3,{c: 1},2]]
        

        相同,因为我认为如果数组元素的顺序不同,则数组不相等。当然,如果需要,这可以更改。此代码还可以进一步增强,以将函数作为参数,用于根据传递的原始值以任意方式格式化 diff 对象(现在这项工作由“compareValues”方法完成)。

        var deepDiffMapper = function () {
          return {
            VALUE_CREATED: 'created',
            VALUE_UPDATED: 'updated',
            VALUE_DELETED: 'deleted',
            VALUE_UNCHANGED: 'unchanged',
            map: function(obj1, obj2) {
              if (this.isFunction(obj1) || this.isFunction(obj2)) {
                throw 'Invalid argument. Function given, object expected.';
              }
              if (this.isValue(obj1) || this.isValue(obj2)) {
                return {
                  type: this.compareValues(obj1, obj2),
                  data: obj1 === undefined ? obj2 : obj1
                };
              }
        
              var diff = {};
              for (var key in obj1) {
                if (this.isFunction(obj1[key])) {
                  continue;
                }
        
                var value2 = undefined;
                if (obj2[key] !== undefined) {
                  value2 = obj2[key];
                }
        
                diff[key] = this.map(obj1[key], value2);
              }
              for (var key in obj2) {
                if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
                  continue;
                }
        
                diff[key] = this.map(undefined, obj2[key]);
              }
        
              return diff;
        
            },
            compareValues: function (value1, value2) {
              if (value1 === value2) {
                return this.VALUE_UNCHANGED;
              }
              if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                return this.VALUE_UNCHANGED;
              }
              if (value1 === undefined) {
                return this.VALUE_CREATED;
              }
              if (value2 === undefined) {
                return this.VALUE_DELETED;
              }
              return this.VALUE_UPDATED;
            },
            isFunction: function (x) {
              return Object.prototype.toString.call(x) === '[object Function]';
            },
            isArray: function (x) {
              return Object.prototype.toString.call(x) === '[object Array]';
            },
            isDate: function (x) {
              return Object.prototype.toString.call(x) === '[object Date]';
            },
            isObject: function (x) {
              return Object.prototype.toString.call(x) === '[object Object]';
            },
            isValue: function (x) {
              return !this.isObject(x) && !this.isArray(x);
            }
          }
        }();
        
        
        var result = deepDiffMapper.map({
          a: 'i am unchanged',
          b: 'i am deleted',
          e: {
            a: 1,
            b: false,
            c: null
          },
          f: [1, {
            a: 'same',
            b: [{
              a: 'same'
            }, {
              d: 'delete'
            }]
          }],
          g: new Date('2017.11.25')
        }, {
          a: 'i am unchanged',
          c: 'i am created',
          e: {
            a: '1',
            b: '',
            d: 'created'
          },
          f: [{
            a: 'same',
            b: [{
              a: 'same'
            }, {
              c: 'create'
            }]
          }, 1],
          g: new Date('2017.11.25')
        });
        console.log(result);

        【讨论】:

        • +1 这不是一段糟糕的代码。但是有一个错误(检查这个例子:jsfiddle.net/kySNu/3c 被创建为undefined,但应该是字符串'i am created'),此外它没有做我需要的,因为它缺少深度数组值比较哪个是最关键(也是最复杂/困难)的部分。作为旁注,构造 'array' != typeof(obj) 是无用的,因为数组是作为数组实例的对象。
        • 我更新了代码,但我不确定您想要在结果对象中得到什么值,现在代码正在从第一个对象返回值,如果它不存在,来自第二个对象的值将设置为数据。
        • 对于{type: ..., data:..} 对象的每个索引,您将获得数组的“缺少深度数组值比较”是什么意思。缺少的是在第二个中从第一个数组中搜索值,但正如我在回答中提到的那样,如果它们的值的顺序不相等,我认为数组不相等(我认为[1, 2, 3] is not equal to [3, 2, 1])。
        • @MartinJespersen 好的,那么您一般如何处理这个数组:[{key: 'value1'}] and [{key: 'value2'}, {key: 'value3'}]。现在是用“value1”或“value2”更新的第一个数组中的第一个对象。这是一个简单的例子,深度嵌套可能会变得非常复杂。如果您想要/需要深度嵌套比较,无论关键位置如何,请不要创建对象数组,请创建具有嵌套对象的对象,如前面的示例:{inner: {key: 'value1'}} and {inner: {key: 'value2'}, otherInner: {key: 'value3'}}
        • 我同意你最后的观点 - 应该将原始数据结构更改为更容易进行实际差异的东西。恭喜,你成功了:)
        【解决方案8】:

        我修改了@sbgoran 的答案,以便生成的差异对象包含仅更改的值,并省略相同的值。此外,它同时显示原始值和更新值

        var deepDiffMapper = function () {
            return {
                VALUE_CREATED: 'created',
                VALUE_UPDATED: 'updated',
                VALUE_DELETED: 'deleted',
                VALUE_UNCHANGED: '---',
                map: function (obj1, obj2) {
                    if (this.isFunction(obj1) || this.isFunction(obj2)) {
                        throw 'Invalid argument. Function given, object expected.';
                    }
                    if (this.isValue(obj1) || this.isValue(obj2)) {
                        let returnObj = {
                            type: this.compareValues(obj1, obj2),
                            original: obj1,
                            updated: obj2,
                        };
                        if (returnObj.type != this.VALUE_UNCHANGED) {
                            return returnObj;
                        }
                        return undefined;
                    }
        
                    var diff = {};
                    let foundKeys = {};
                    for (var key in obj1) {
                        if (this.isFunction(obj1[key])) {
                            continue;
                        }
        
                        var value2 = undefined;
                        if (obj2[key] !== undefined) {
                            value2 = obj2[key];
                        }
        
                        let mapValue = this.map(obj1[key], value2);
                        foundKeys[key] = true;
                        if (mapValue) {
                            diff[key] = mapValue;
                        }
                    }
                    for (var key in obj2) {
                        if (this.isFunction(obj2[key]) || foundKeys[key] !== undefined) {
                            continue;
                        }
        
                        let mapValue = this.map(undefined, obj2[key]);
                        if (mapValue) {
                            diff[key] = mapValue;
                        }
                    }
        
                    //2020-06-13: object length code copied from https://stackoverflow.com/a/13190981/2336212
                    if (Object.keys(diff).length > 0) {
                        return diff;
                    }
                    return undefined;
                },
                compareValues: function (value1, value2) {
                    if (value1 === value2) {
                        return this.VALUE_UNCHANGED;
                    }
                    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                        return this.VALUE_UNCHANGED;
                    }
                    if (value1 === undefined) {
                        return this.VALUE_CREATED;
                    }
                    if (value2 === undefined) {
                        return this.VALUE_DELETED;
                    }
                    return this.VALUE_UPDATED;
                },
                isFunction: function (x) {
                    return Object.prototype.toString.call(x) === '[object Function]';
                },
                isArray: function (x) {
                    return Object.prototype.toString.call(x) === '[object Array]';
                },
                isDate: function (x) {
                    return Object.prototype.toString.call(x) === '[object Date]';
                },
                isObject: function (x) {
                    return Object.prototype.toString.call(x) === '[object Object]';
                },
                isValue: function (x) {
                    return !this.isObject(x) && !this.isArray(x);
                }
            }
        }();
        

        【讨论】:

        • Ty,原文太冗长了。但我不得不进一步修改它以检查类型数组...isArray: x =&gt; Array.isArray(x) || ArrayBuffer.isView(x)
        • 这是一个不错的改进,但值得一提的是,如果两个对象匹配,那么它只会返回 undefined :)
        【解决方案9】:

        这是一个解决方案:

        • Typescript(但可轻松转换为 Javascript)
        • 没有库依赖项
        • 通用,不关心检查对象类型(object 类型除外)
        • 支持值为undefined的属性
        • 深不可测(默认)

        首先我们定义比较结果接口:

        export interface ObjectComparison {
          added: {};
          updated: {
            [propName: string]: Change;
          };
          removed: {};
          unchanged: {};
        }
        

        对于变化的特殊情况,我们想知道什么是旧值和新值:

        export interface Change {
          oldValue: any;
          newValue: any;
        }
        

        然后我们可以提供diff函数,它只是两个循环(如果deeptrue,则具有递归性):

        export class ObjectUtils {
        
          static diff(o1: {}, o2: {}, deep = false): ObjectComparison {
            const added = {};
            const updated = {};
            const removed = {};
            const unchanged = {};
            for (const prop in o1) {
              if (o1.hasOwnProperty(prop)) {
                const o2PropValue = o2[prop];
                const o1PropValue = o1[prop];
                if (o2.hasOwnProperty(prop)) {
                  if (o2PropValue === o1PropValue) {
                    unchanged[prop] = o1PropValue;
                  } else {
                    updated[prop] = deep && this.isObject(o1PropValue) && this.isObject(o2PropValue) ? this.diff(o1PropValue, o2PropValue, deep) : {newValue: o2PropValue};
                  }
                } else {
                  removed[prop] = o1PropValue;
                }
              }
            }
            for (const prop in o2) {
              if (o2.hasOwnProperty(prop)) {
                const o1PropValue = o1[prop];
                const o2PropValue = o2[prop];
                if (o1.hasOwnProperty(prop)) {
                  if (o1PropValue !== o2PropValue) {
                    if (!deep || !this.isObject(o1PropValue)) {
                      updated[prop].oldValue = o1PropValue;
                    }
                  }
                } else {
                  added[prop] = o2PropValue;
                }
              }
            }
            return { added, updated, removed, unchanged };
          }
        
          /**
           * @return if obj is an Object, including an Array.
           */
          static isObject(obj: any) {
            return obj !== null && typeof obj === 'object';
          }
        }
        

        例如,调用:

        ObjectUtils.diff(
          {
            a: 'a', 
            b: 'b', 
            c: 'c', 
            arr: ['A', 'B'], 
            obj: {p1: 'p1', p2: 'p2'}
          },
          {
            b: 'x', 
            c: 'c', 
            arr: ['B', 'C'], 
            obj: {p2: 'p2', p3: 'p3'}, 
            d: 'd'
          },
        );
        

        将返回:

        {
          added: {d: 'd'},
          updated: {
            b: {oldValue: 'b', newValue: 'x'},
            arr: {oldValue: ['A', 'B'], newValue: ['B', 'C']},
            obj: {oldValue: {p1: 'p1', p2: 'p2'}, newValue: {p2: 'p2', p3: 'p3'}}
          },
          removed: {a: 'a'},
          unchanged: {c: 'c'},
        }
        

        并使用deep 第三个参数调用相同的参数将返回:

        {
          added: {d: 'd'},
          updated: {
            b: {oldValue: 'b', newValue: 'x'},
            arr: {
              added: {},
              removed: {},
              unchanged: {},
              updated: {
                0: {oldValue: 'A', newValue: 'B'},
                1: {oldValue: 'B', newValue: 'C', }
              }
            },
            obj: {
              added: {p3: 'p3'},
              removed: {p1: 'p1'},
              unchanged: {p2: 'p2'},
              updated: {}
            }
          },
          removed: {a: 'a'},
          unchanged: {c: 'c'},
        }
        

        【讨论】:

          【解决方案10】:

          这是在 gisthub 上找到的内容的修改版本。

          isNullBlankOrUndefined = function (o) {
              return (typeof o === "undefined" || o == null || o === "");
          }
          
          /**
           * Deep diff between two object, using lodash
           * @param  {Object} object Object compared
           * @param  {Object} base   Object to compare with
           * @param  {Object} ignoreBlanks will not include properties whose value is null, undefined, etc.
           * @return {Object}        Return a new object who represent the diff
           */
          objectDifference = function (object, base, ignoreBlanks = false) {
              if (!lodash.isObject(object) || lodash.isDate(object)) return object            // special case dates
              return lodash.transform(object, (result, value, key) => {
                  if (!lodash.isEqual(value, base[key])) {
                      if (ignoreBlanks && du.isNullBlankOrUndefined(value) && isNullBlankOrUndefined( base[key])) return;
                      result[key] = lodash.isObject(value) && lodash.isObject(base[key]) ? objectDifference(value, base[key]) : value;
                  }
              });
          }
          

          【讨论】:

            【解决方案11】:

            我通过@sbgoran 接受了上面的答案,并针对我的情况进行了修改,与所需的问题相同,将数组视为集合(即顺序对于差异并不重要)

            const deepDiffMapper = function () {
            return {
              VALUE_CREATED: "created",
              VALUE_UPDATED: "updated",
              VALUE_DELETED: "deleted",
              VALUE_UNCHANGED: "unchanged",
              map: function(obj1: any, obj2: any) {
                if (this.isFunction(obj1) || this.isFunction(obj2)) {
                  throw "Invalid argument. Function given, object expected.";
                }
                if (this.isValue(obj1) || this.isValue(obj2)) {
                  return {
                    type: this.compareValues(obj1, obj2),
                    data: obj2 === undefined ? obj1 : obj2
                  };
                }
            
                if (this.isArray(obj1) || this.isArray(obj2)) {
                  return {
                    type: this.compareArrays(obj1, obj2),
                    data: this.getArrayDiffData(obj1, obj2)
                  };
                }
            
                const diff: any = {};
                for (const key in obj1) {
            
                  if (this.isFunction(obj1[key])) {
                    continue;
                  }
            
                  let value2 = undefined;
                  if (obj2[key] !== undefined) {
                    value2 = obj2[key];
                  }
            
                  diff[key] = this.map(obj1[key], value2);
                }
                for (const key in obj2) {
                  if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
                    continue;
                  }
            
                  diff[key] = this.map(undefined, obj2[key]);
                }
            
                return diff;
            
              },
            
              getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) {
                const set1 = new Set(arr1);
                const set2 = new Set(arr2);
            
                if (arr1 === undefined || arr2 === undefined) {
                   return arr1 === undefined ? arr1 : arr2;
                }
                const deleted = [...arr1].filter(x => !set2.has(x));
            
                const added = [...arr2].filter(x => !set1.has(x));
            
                return {
                  added, deleted
                };
            
              },
            
              compareArrays: function(arr1: Array<any>, arr2: Array<any>) {
                const set1 = new Set(arr1);
                const set2 = new Set(arr2);
                if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) {
                  return this.VALUE_UNCHANGED;
                }
                if (arr1 === undefined) {
                  return this.VALUE_CREATED;
                }
                if (arr2 === undefined) {
                  return this.VALUE_DELETED;
                }
                return this.VALUE_UPDATED;
              },
              compareValues: function (value1: any, value2: any) {
                if (value1 === value2) {
                  return this.VALUE_UNCHANGED;
                }
                if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                  return this.VALUE_UNCHANGED;
                }
                if (value1 === undefined) {
                  return this.VALUE_CREATED;
                }
                if (value2 === undefined) {
                  return this.VALUE_DELETED;
                }
                return this.VALUE_UPDATED;
              },
              isFunction: function (x: any) {
                return Object.prototype.toString.call(x) === "[object Function]";
              },
              isArray: function (x: any) {
                return Object.prototype.toString.call(x) === "[object Array]";
              },
              isDate: function (x: any) {
                return Object.prototype.toString.call(x) === "[object Date]";
              },
              isObject: function (x: any) {
                return Object.prototype.toString.call(x) === "[object Object]";
              },
              isValue: function (x: any) {
                return !this.isObject(x) && !this.isArray(x);
              }
             };
            }();
            

            【讨论】:

              【解决方案12】:

              我偶然发现这里试图寻找一种方法来获得两个对象之间的差异。这是我使用 Lodash 的解决方案:

              // Get updated values (including new values)
              var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));
              
              // Get updated values (excluding new values)
              var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));
              
              // Get old values (by using updated values)
              var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});
              
              // Get newly added values
              var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));
              
              // Get removed values
              var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));
              
              // Then you can group them however you want with the result
              

              下面的代码sn-p:

              var last = {
              "authed": true,
              "inForeground": true,
              "goodConnection": false,
              "inExecutionMode": false,
              "online": true,
              "array": [1, 2, 3],
              "deep": {
              	"nested": "value",
              },
              "removed": "value",
              };
              
              var curr = {
              "authed": true,
              "inForeground": true,
              "deep": {
              	"nested": "changed",
              },
              "array": [1, 2, 4],
              "goodConnection": true,
              "inExecutionMode": false,
              "online": false,
              "new": "value"
              };
              
              // Get updated values (including new values)
              var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));
              // Get updated values (excluding new values)
              var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));
              // Get old values (by using updated values)
              var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});
              // Get newly added values
              var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));
              // Get removed values
              var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));
              
              console.log('oldValues', JSON.stringify(oldValues));
              console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl));
              console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl));
              console.log('newCreatedValues', JSON.stringify(newCreatedValues));
              console.log('deletedValues', JSON.stringify(deletedValues));
              &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"&gt;&lt;/script&gt;

              【讨论】:

                【解决方案13】:
                const diff = require("deep-object-diff").diff;
                let differences = diff(obj2, obj1);
                

                有一个每周下载量超过 50 万的 npm 模块:https://www.npmjs.com/package/deep-object-diff

                我喜欢对象之类的差异表示——尤其是当它被格式化时很容易看到结构。

                const diff = require("deep-object-diff").diff;
                
                const lhs = {
                  foo: {
                    bar: {
                      a: ['a', 'b'],
                      b: 2,
                      c: ['x', 'y'],
                      e: 100 // deleted
                    }
                  },
                  buzz: 'world'
                };
                
                const rhs = {
                  foo: {
                    bar: {
                      a: ['a'], // index 1 ('b')  deleted
                      b: 2, // unchanged
                      c: ['x', 'y', 'z'], // 'z' added
                      d: 'Hello, world!' // added
                    }
                  },
                  buzz: 'fizz' // updated
                };
                
                console.log(diff(lhs, rhs)); // =>
                /*
                {
                  foo: {
                    bar: {
                      a: {
                        '1': undefined
                      },
                      c: {
                        '2': 'z'
                      },
                      d: 'Hello, world!',
                      e: undefined
                    }
                  },
                  buzz: 'fizz'
                }
                */
                

                【讨论】:

                • 现在应该标记为正确答案吗?
                • 就是这样
                • 不幸的是,它看起来从未添加“移动”支持,这对于保留对象引用非常重要,而不是盲目地重建恰好具有相同对象形状的惰性内容。
                【解决方案14】:

                这是@sbgoran 代码的打字稿版本

                export class deepDiffMapper {
                
                  static VALUE_CREATED = 'created';
                  static VALUE_UPDATED = 'updated';
                  static VALUE_DELETED = 'deleted';
                  static VALUE_UNCHANGED ='unchanged';
                
                  protected isFunction(obj: object) {
                    return {}.toString.apply(obj) === '[object Function]';
                  };
                
                  protected isArray(obj: object) {
                      return {}.toString.apply(obj) === '[object Array]';
                  };
                
                  protected isObject(obj: object) {
                      return {}.toString.apply(obj) === '[object Object]';
                  };
                
                  protected isDate(obj: object) {
                      return {}.toString.apply(obj) === '[object Date]';
                  };
                
                  protected isValue(obj: object) {
                      return !this.isObject(obj) && !this.isArray(obj);
                  };
                
                  protected compareValues (value1: any, value2: any) {
                    if (value1 === value2) {
                        return deepDiffMapper.VALUE_UNCHANGED;
                    }
                    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                        return deepDiffMapper.VALUE_UNCHANGED;
                    }
                    if ('undefined' == typeof(value1)) {
                        return deepDiffMapper.VALUE_CREATED;
                    }
                    if ('undefined' == typeof(value2)) {
                        return deepDiffMapper.VALUE_DELETED;
                    }
                
                    return deepDiffMapper.VALUE_UPDATED;
                  }
                
                  public map(obj1: object, obj2: object) {
                      if (this.isFunction(obj1) || this.isFunction(obj2)) {
                          throw 'Invalid argument. Function given, object expected.';
                      }
                      if (this.isValue(obj1) || this.isValue(obj2)) {
                          return {
                              type: this.compareValues(obj1, obj2),
                              data: (obj1 === undefined) ? obj2 : obj1
                          };
                      }
                
                      var diff = {};
                      for (var key in obj1) {
                          if (this.isFunction(obj1[key])) {
                              continue;
                          }
                
                          var value2 = undefined;
                          if ('undefined' != typeof(obj2[key])) {
                              value2 = obj2[key];
                          }
                
                          diff[key] = this.map(obj1[key], value2);
                      }
                      for (var key in obj2) {
                          if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) {
                              continue;
                          }
                
                          diff[key] = this.map(undefined, obj2[key]);
                      }
                
                      return diff;
                
                  }
                }
                

                【讨论】:

                  【解决方案15】:

                  使用 Lodash:

                  _.mergeWith(oldObj, newObj, function (objectValue, sourceValue, key, object, source) {
                      if ( !(_.isEqual(objectValue, sourceValue)) && (Object(objectValue) !== objectValue)) {
                          console.log(key + "\n    Expected: " + sourceValue + "\n    Actual: " + objectValue);
                      }
                  });

                  我不使用密钥/对象/源,但如果您需要访问它们,我会将其留在其中。 对象比较只是防止控制台将差异从最外层元素打印到最内层元素。

                  你可以在里面添加一些逻辑来处理数组。也许首先对数组进行排序。这是一个非常灵活的解决方案。

                  编辑

                  由于 lodash 更新,从 _.merge 更改为 _.mergeWith。感谢 Aviron 注意到这一变化。

                  【讨论】:

                  • 在 lodash 4.15.0 中不再支持带有定制器功能的 _.merge,因此您应该改用 _.mergeWith。
                  • 这个功能很棒,但在嵌套对象中不起作用。
                  【解决方案16】:

                  来自 sbgoran 的回答中更扩展和简化的功能。
                  这允许深度扫描并找到数组的相似性。

                  var result = objectDifference({
                        a:'i am unchanged',
                        b:'i am deleted',
                        e: {a: 1,b:false, c: null},
                        f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],
                        g: new Date('2017.11.25'),
                        h: [1,2,3,4,5]
                    },
                    {
                        a:'i am unchanged',
                        c:'i am created',
                        e: {a: '1', b: '', d:'created'},
                        f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],
                        g: new Date('2017.11.25'),
                        h: [4,5,6,7,8]
                    });
                  console.log(result);
                  
                  function objectDifference(obj1, obj2){
                      if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){
                          var type = '';
                  
                          if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime()))
                              type = 'unchanged';
                          else if(dataType(obj1) === 'undefined')
                              type = 'created';
                          if(dataType(obj2) === 'undefined')
                              type = 'deleted';
                          else if(type === '') type = 'updated';
                  
                          return {
                              type: type,
                              data:(obj1 === undefined) ? obj2 : obj1
                          };
                      }
                    
                      if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){
                          var diff = [];
                          obj1.sort(); obj2.sort();
                          for(var i = 0; i < obj2.length; i++){
                              var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged';
                              if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){
                                  diff.push(
                                      objectDifference(obj1[i], obj2[i])
                                  );
                                  continue;
                              }
                              diff.push({
                                  type: type,
                                  data: obj2[i]
                              });
                          }
                  
                          for(var i = 0; i < obj1.length; i++){
                              if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object')
                                  continue;
                              diff.push({
                                  type: 'deleted',
                                  data: obj1[i]
                              });
                          }
                      } else {
                          var diff = {};
                          var key = Object.keys(obj1);
                          for(var i = 0; i < key.length; i++){
                              var value2 = undefined;
                              if(dataType(obj2[key[i]]) !== 'undefined')
                                  value2 = obj2[key[i]];
                  
                              diff[key[i]] = objectDifference(obj1[key[i]], value2);
                          }
                  
                          var key = Object.keys(obj2);
                          for(var i = 0; i < key.length; i++){
                              if(dataType(diff[key[i]]) !== 'undefined')
                                  continue;
                  
                              diff[key[i]] = objectDifference(undefined, obj2[key[i]]);
                          }
                      }
                  
                      return diff;
                  }
                  
                  function dataType(data){
                      if(data === undefined || data === null) return 'undefined';
                      if(data.constructor === String) return 'string';
                      if(data.constructor === Array) return 'array';
                      if(data.constructor === Object) return 'object';
                      if(data.constructor === Number) return 'number';
                      if(data.constructor === Boolean) return 'boolean';
                      if(data.constructor === Function) return 'function';
                      if(data.constructor === Date) return 'date';
                      if(data.constructor === RegExp) return 'regex';
                      return 'unknown';
                  }

                  【讨论】:

                    【解决方案17】:

                    我只是使用 ramda,为了解决同样的问题,我需要知道新对象的变化。所以这里是我的设计。

                    const oldState = {id:'170',name:'Ivab',secondName:'Ivanov',weight:45};
                    const newState = {id:'170',name:'Ivanko',secondName:'Ivanov',age:29};
                    
                    const keysObj1 = R.keys(newState)
                    
                    const filterFunc = key => {
                      const value = R.eqProps(key,oldState,newState)
                      return {[key]:value}
                    }
                    
                    const result = R.map(filterFunc, keysObj1)
                    

                    结果是,属性的​​名称和它的状态。

                    [{"id":true}, {"name":false}, {"secondName":true}, {"age":false}]
                    

                    【讨论】:

                    • R 代表什么??
                    • @SomeoneSpecial 是 ramda js 库ramdajs.com
                    【解决方案18】:

                    我在 Javascript 中开发了名为“compareValue()”的函数。 它返回值是否相同。 我在一个对象的 for 循环中调用了 compareValue()。 您可以在 diffParams 中获得两个对象的差异。

                    var diffParams = {};
                    var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]},
                        obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]};
                    
                    for( var p in obj1 ){
                      if ( !compareValue(obj1[p], obj2[p]) ){
                        diffParams[p] = obj1[p];
                      }
                    }
                    
                    function compareValue(val1, val2){
                      var isSame = true;
                      for ( var p in val1 ) {
                    
                        if (typeof(val1[p]) === "object"){
                          var objectValue1 = val1[p],
                              objectValue2 = val2[p];
                          for( var value in objectValue1 ){
                            isSame = compareValue(objectValue1[value], objectValue2[value]);
                            if( isSame === false ){
                              return false;
                            }
                          }
                        }else{
                          if(val1 !== val2){
                            isSame = false;
                          }
                        }
                      }
                      return isSame;
                    }
                    console.log(diffParams);

                    【讨论】:

                      【解决方案19】:

                      我想提供一个 ES6 解决方案...这是一种单向差异,这意味着它将从 o2 返回与 o1 中的对应项不同的键/值:

                      let o1 = {
                        one: 1,
                        two: 2,
                        three: 3
                      }
                      
                      let o2 = {
                        two: 2,
                        three: 3,
                        four: 4
                      }
                      
                      let diff = Object.keys(o2).reduce((diff, key) => {
                        if (o1[key] === o2[key]) return diff
                        return {
                          ...diff,
                          [key]: o2[key]
                        }
                      }, {})
                      

                      【讨论】:

                      • 不错的解决方案,但您可能需要检查 if(o1[key] === o1[key]) 行老兄
                      • 代码是否完整?我收到Uncaught SyntaxError: Unexpected token ...
                      • 我喜欢这个解决方案,但它有一个问题,如果对象比一层更深,它将返回更改的嵌套对象中的所有值 - 或者至少对我来说是这样。跨度>
                      • 是的,这不是递归@Spurious
                      • 请记住,使用此解决方案,对于对象中的每个元素,您都会获得一个全新的对象,其中包含复制到其中的所有现有元素,只需将一项添加到数组中。对于小物体,这很好,但对于较大的物体,它会成倍减慢。
                      【解决方案20】:

                      我已经为我的一个项目编写了一个函数,它将作为用户选项的对象与其内部克隆进行比较。 如果用户在纯 javascript 中输入错误类型的数据或被删除,它还​​可以验证甚至替换默认值。

                      在 IE8 中 100% 有效。测试成功。

                      //  ObjectKey: ["DataType, DefaultValue"]
                      reference = { 
                          a : ["string", 'Defaul value for "a"'],
                          b : ["number", 300],
                          c : ["boolean", true],
                          d : {
                              da : ["boolean", true],
                              db : ["string", 'Defaul value for "db"'],
                              dc : {
                                  dca : ["number", 200],
                                  dcb : ["string", 'Default value for "dcb"'],
                                  dcc : ["number", 500],
                                  dcd : ["boolean", true]
                            },
                            dce : ["string", 'Default value for "dce"'],
                          },
                          e : ["number", 200],
                          f : ["boolean", 0],
                          g : ["", 'This is an internal extra parameter']
                      };
                      
                      userOptions = { 
                          a : 999, //Only string allowed
                        //b : ["number", 400], //User missed this parameter
                          c: "Hi", //Only lower case or case insitive in quotes true/false allowed.
                          d : {
                              da : false,
                              db : "HelloWorld",
                              dc : {
                                  dca : 10,
                                  dcb : "My String", //Space is not allowed for ID attr
                                  dcc: "3thString", //Should not start with numbers
                                  dcd : false
                            },
                            dce: "ANOTHER STRING",
                          },
                          e: 40,
                          f: true,
                      };
                      
                      
                      function compare(ref, obj) {
                      
                          var validation = {
                              number: function (defaultValue, userValue) {
                                if(/^[0-9]+$/.test(userValue))
                                  return userValue;
                                else return defaultValue;
                              },
                              string: function (defaultValue, userValue) {
                                if(/^[a-z][a-z0-9-_.:]{1,51}[^-_.:]$/i.test(userValue)) //This Regex is validating HTML tag "ID" attributes
                                  return userValue;
                                else return defaultValue;
                              },
                              boolean: function (defaultValue, userValue) {
                                if (typeof userValue === 'boolean')
                                  return userValue;
                                else return defaultValue;
                              }
                          };
                      
                          for (var key in ref)
                              if (obj[key] && obj[key].constructor && obj[key].constructor === Object)
                                ref[key] = compare(ref[key], obj[key]);
                              else if(obj.hasOwnProperty(key))
                                ref[key] = validation[ref[key][0]](ref[key][1], obj[key]); //or without validation on user enties => ref[key] = obj[key]
                              else ref[key] = ref[key][1];
                          return ref;
                      }
                      
                      //console.log(
                          alert(JSON.stringify( compare(reference, userOptions),null,2 ))
                      //);
                      

                      /*结果

                      {
                        "a": "Defaul value for \"a\"",
                        "b": 300,
                        "c": true,
                        "d": {
                          "da": false,
                          "db": "Defaul value for \"db\"",
                          "dc": {
                            "dca": 10,
                            "dcb": "Default value for \"dcb\"",
                            "dcc": 500,
                            "dcd": false
                          },
                          "dce": "Default value for \"dce\""
                        },
                        "e": 40,
                        "f": true,
                        "g": "This is an internal extra parameter"
                      }
                      
                      */
                      

                      【讨论】:

                        【解决方案21】:

                        使用下划线,一个简单的差异:

                        var o1 = {a: 1, b: 2, c: 2},
                            o2 = {a: 2, b: 1, c: 2};
                        
                        _.omit(o1, function(v,k) { return o2[k] === v; })
                        

                        o1 中对应但在o2 中具有不同值的部分的结果:

                        {a: 1, b: 2}
                        

                        深度差异会有所不同:

                        function diff(a,b) {
                            var r = {};
                            _.each(a, function(v,k) {
                                if(b[k] === v) return;
                                // but what if it returns an empty object? still attach?
                                r[k] = _.isObject(v)
                                        ? _.diff(v, b[k])
                                        : v
                                    ;
                                });
                            return r;
                        }
                        

                        正如@Juhana 在 cmets 中指出的那样,以上只是一个差异 a-->b 而不是 reversible (意味着 b 中的额外属性将被忽略)。改用 a-->b-->a:

                        (function(_) {
                          function deepDiff(a, b, r) {
                            _.each(a, function(v, k) {
                              // already checked this or equal...
                              if (r.hasOwnProperty(k) || b[k] === v) return;
                              // but what if it returns an empty object? still attach?
                              r[k] = _.isObject(v) ? _.diff(v, b[k]) : v;
                            });
                          }
                        
                          /* the function */
                          _.mixin({
                            diff: function(a, b) {
                              var r = {};
                              deepDiff(a, b, r);
                              deepDiff(b, a, r);
                              return r;
                            }
                          });
                        })(_.noConflict());
                        

                        查看http://jsfiddle.net/drzaus/9g5qoxwj/ 了解完整示例+测试+mixins

                        【讨论】:

                        • 不知道为什么你被否决了,这已经足够了,因为你提供了一个浅层、简单的例子以及一个更复杂的深层函数。
                        • @Seiyria 讨厌的人会讨厌,我猜...我两个都做了,因为我最初认为 omit 会有很大的差异,但是错了,所以也包括在内以进行比较。
                        • 不错的解决方案。我建议把r[k] = ... : v改成r[k] = ... : {'a':v, 'b':b[k] },这样你就可以看到两个值了。
                        • 当对象在其他方面相同但第二个具有更多元素时,这两个都返回假阴性,例如{a:1, b:2}{a:1, b:2, c:3}.
                        • 应该是_.omitBy 而不是_.omit
                        【解决方案22】:

                        如今,有很多可用的模块。我最近编写了一个模块来执行此操作,因为我对找到的众多不同模块并不满意。它称为 odiff: https://github.com/Tixit/odiff 。我还列出了一些最受欢迎的模块以及为什么它们在odiff 的自述文件中不被接受,如果odiff 没有你想要的属性,你可以看一下。这是一个例子:

                        var a = [{a:1,b:2,c:3},              {x:1,y: 2, z:3},              {w:9,q:8,r:7}]
                        var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]
                        
                        var diffs = odiff(a,b)
                        
                        /* diffs now contains:
                        [{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
                         {type: 'set', path:[1,'y'], val: '3'},
                         {type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
                        ]
                        */
                        

                        【讨论】:

                        • 这看起来仍然缺少move 操作。并非所有内容都是设置、未设置或更新。
                        【解决方案23】:

                        我已经使用这段代码来完成您描述的任务:

                        function mergeRecursive(obj1, obj2) {
                            for (var p in obj2) {
                                try {
                                    if(obj2[p].constructor == Object) {
                                        obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                                    }
                                    // Property in destination object set; update its value.
                                    else if (Ext.isArray(obj2[p])) {
                                        // obj1[p] = [];
                                        if (obj2[p].length < 1) {
                                            obj1[p] = obj2[p];
                                        }
                                        else {
                                            obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                                        }
                        
                                    }else{
                                        obj1[p] = obj2[p];
                                    }
                                } catch (e) {
                                    // Property in destination object not set; create it and set its value.
                                    obj1[p] = obj2[p];
                                }
                            }
                            return obj1;
                        }
                        

                        这将为您提供一个新对象,它将合并旧对象和表单中新对象之间的所有更改

                        【讨论】:

                        • 我在这里使用的是 Ext 框架,但您可以替换它并使用您喜欢的任何其他框架...
                        • 合并对象很简单,可以像 $.extend(true,obj1,obj2) 使用 jQuery 一样简单。这根本不是我需要的。我需要两个对象之间的区别,而不是它们的组合。
                        • 这里使用Ext很棒
                        猜你喜欢
                        • 1970-01-01
                        • 2017-03-08
                        • 1970-01-01
                        • 2012-04-23
                        • 2021-10-16
                        • 2012-09-07
                        • 2013-04-12
                        • 2022-01-04
                        • 1970-01-01
                        相关资源
                        最近更新 更多