我有两个对象:oldObj和newObj。

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

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

现在我不仅需要弄清楚从oldObj到newObj更改了什么(如添加/更新/删除),而且还需要知道如何最好地表示它。

到目前为止,我的想法只是构建一个通用的deepdiffbetweenobjects方法,该方法将返回窗体上的对象{add:{…},upd:{…},del:{…但我转念一想:以前一定有人需要它。

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

更新:

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

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

如果newObj。prop1 = 'new value'和oldObj。prop1 = 'old value'它将设置returnbj。Prop1 = {type: 'update', data: 'new value'}

更新2:

当我们处理数组的属性时,它会变得非常麻烦,因为数组[1,2,3]应该被计算为等于[2,3,1],这对于基于值的类型的数组(如string, int和bool)来说足够简单,但是当涉及到引用类型的数组(如对象和数组)时就很难处理了。

数组的示例应该是相等的:

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

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


当前回答

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

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;
}

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

其他回答

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

我使用了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]);

希望这能帮助到一些人。

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

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

没有使用_。omitBy是因为性能影响。

下面是@sbgoran代码的typescript版本

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;

  }
}
function Difference (ob1,ob2){   
 let ob3={}  
 let status=false  
  for ( var a1 in ob1 ) {  
    for (var a2 in ob2){  
      if (a1===a2 && ob1[a1]===ob2[a2]){     
        status=true;   
        break;   
      };
    };    
    if (status===false){    
      if (ob1[a2]===undefined){    
        ob3[a1]="ob1:"+ob1[a1]+", ob2:"+ob2[a1];    
      };    
      if ( ob2[a1]===undefined){    
        ob3[a2]="ob1:"+ob1[a2]+", ob2:"+ob2[a2];    
      }else {    
        ob3[a1]="ob1:"+ob1[a1] +", ob2:"+ob2[a1];    
      };     
    }else {   
      status=false;
    };   
  };     
  console.log(ob3);    
};

如今,有相当多的模块可用于此。我最近写了一个模块来做到这一点,因为我不满意我发现的大量不同的模块。它被称为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}]}
]
*/