【问题标题】:jQuery function to compute the difference between two JavaScript objects用于计算两个 JavaScript 对象之间差异的 jQuery 函数
【发布时间】:2012-06-16 19:47:42
【问题描述】:

我有一个丰富的基于 AJAX 的 Web 应用程序,它使用 JQuery + Knockout。我有一个 JQuery 插件,它包装了我的 Knockout 视图模型,以公开 .reset().isDirty() 等实用方法。

我有一个名为 .setBaseline() 的方法,该方法本质上是在填充数据模型后(通过映射插件)获取数据模型的快照。然后我可以使用这个快照来快速确定模型是否发生了变化。

我正在寻找的是某种通用函数,它可以返回一个对象,该对象代表两个 JavaScript 对象之间的差异,其中一个对象是被认为是主人。

例如,假设这是我的快照:

var snapShot = {
  name: "Joe",
  address: "123 Main Street",
  age: 30,
  favoriteColorPriority: {
     yellow: 1,
     pink: 2,
     blue: 3
  }
};

然后假设实时数据如下所示:

var liveData = {
    name: "Joseph",
    address: "123 Main Street",
    age: 30,
    favoriteColorPriority: {
        yellow: 1,
        pink: 3,
        blue: 2
    }
};

我想要一个返回以下内容的 .getChanges(snapShot, liveData) 实用函数:

var differences = {
    name: "Joseph",
    favoriteColorPriority: {
        pink: 3,
        blue: 2
    }
};

我希望 _.underscore 库 可能有类似的东西,但我找不到任何看起来像这样工作的东西。

【问题讨论】:

  • 今天早上我看到了同样的问题。
  • 聪明的头脑都一样! :-) 你有链接吗?
  • 谢谢。我正在查看该问题中的建议,看看是否有适合我的用例。到目前为止,该问题的作者还没有接受答案,所以我想让这个问题运行更长时间。

标签: javascript jquery json knockout.js underscore.js


【解决方案1】:

我觉得下划线里面没有这样的功能,但是自己实现很容易:

function getChanges(prev, now) {
    var changes = {};
    for (var prop in now) {
        if (!prev || prev[prop] !== now[prop]) {
            if (typeof now[prop] == "object") {
                var c = getChanges(prev[prop], now[prop]);
                if (! _.isEmpty(c) ) // underscore
                    changes[prop] = c;
            } else {
                changes[prop] = now[prop];
            }
        }
    }
    return changes;
}

function getChanges(prev, now) {
    var changes = {}, prop, pc;
    for (prop in now) {
        if (!prev || prev[prop] !== now[prop]) {
            if (typeof now[prop] == "object") {
                if(c = getChanges(prev[prop], now[prop]))
                    changes[prop] = c;
            } else {
                changes[prop] = now[prop];
            }
        }
    }
    for (prop in changes)
        return changes;
    return false; // false when unchanged
}

这不适用于数组(或任何其他非普通对象)或不同结构的对象(删除、原始对象类型的更改)。

【讨论】:

  • 删除了我的答案以支持这个。
  • 这些现在使用真实数据。我忘记提及的一件事是,如果 snapShot 包含当前(实时)数据中缺失的数据,那么我不想在 changeList 中看到这些值。例如,我的 shapShot 数据模型包含一个名为“businessUnitProperties: {}”的空对象,这可能是一个键/值对。如果实时数据没有这个成员,那么更改也不应该返回它。
  • 一个可能的问题可能源于使用了众所周知的不可靠的typeof。只要您知道注意事项,您就应该没问题。
  • 对于 typeof() 问题,我将使用 _.underscore 包装器 _.isObject()。但是,到目前为止一切顺利。
  • 是的,当然。您能否在问题中的示例中添加一个,以便我可以看到您希望如何更改它们?简单的方法是将它们视为原始值,只需在 typeof now[prop] == "object" 之后添加 && !Array.isArray(now[prop])...
【解决方案2】:

发布我自己的答案,以便人们可以看到也适用于数组的最终实现。在下面的代码中,“um”是我的命名空间,我还使用了 Underscore.js 中的 _.isArray() 和 _.isObject 方法。

查找“_KO”的代码用于跳过对象中存在的 Knockout.js 成员。

// This function compares 'now' to 'prev' and returns a new JavaScript object that contains only
// differences between 'now' and 'prev'. If 'prev' contains members that are missing from 'now',
// those members are *not* returned. 'now' is treated as the master list.
um.utils.getChanges = function (prev, now) {
    var changes = {};
    var prop = {};
    var c = {};
    //-----

    for (prop in now) { //ignore jslint
        if (prop.indexOf("_KO") > -1) {
            continue; //ignore jslint
        }

        if (!prev || prev[prop] !== now[prop]) {
            if (_.isArray(now[prop])) {
                changes[prop] = now[prop];
            }
            else if (_.isObject(now[prop])) {
                // Recursion alert
                c = um.utils.getChanges(prev[prop], now[prop]);
                if (!_.isEmpty(c)) {
                    changes[prop] = c;
                }
            } else {
                changes[prop] = now[prop];
            }
        }
    }

    return changes;
};

【讨论】:

    【解决方案3】:

    我在我的项目中使用JsonDiffPatch 来查找增量,通过网络传输它,然后在另一端修补对象以获得准确的副本。它非常易于使用并且效果非常好。

    它也适用于数组!

    【讨论】:

    • 我来看看这个库。我已经使用上面的代码作为起点定制了我自己的解决方案,所以当我现在拥有的东西没有被破坏时,在游戏中切换到新的东西可能为时已晚。但很高兴看到一些独立库以通用方式进行此类比较。
    【解决方案4】:

    使用 jQuery.isEmptyObject() 的解决方案

    这会修改上面的 getChanges(prev, now) 解决方案。它也应该适用于不同类型的对象,也适用于 prev[prop] 未定义(导致 now[prop])

    function getChanges(prev, now)
    {
        // sanity checks, now and prev must be an objects
        if (typeof now !== "object")
            now = {};
        if (typeof prev !== "object")
            return now;
    
        var changes = {};
        for (var prop in now) {
             // if prop is new in now, add it to changes
            if (!prev || !prev.hasOwnProperty(prop) ) {
                changes[prop] = now[prop];
                continue;
            }
            // if prop has another type or value (different object or literal)
            if (prev[prop] !== now[prop]) {
                if (typeof now[prop] === "object") {
                    // prop is an object, do recursion
                    var c = getChanges(prev[prop], now[prop]);
                    if (!$.isEmptyObject(c))
                        changes[prop] = c;
                } else {
                    // now[prop] has different but literal value
                    changes[prop] = now[prop];
                }
            }
        }
    
        // returns empty object on none change
        return changes;
    }
    

    【讨论】:

    • 这似乎是一个可行的解决方案,除了在我尝试过的大多数大型嵌套对象上产生“未捕获的 RangeError:超出最大调用堆栈大小”。如果您将代码调整为不使用 jQuery,您可能能够从中获得性能提升。当然理论上。不过,这对较小的嵌套对象非常有效。
    【解决方案5】:

    不需要为此使用 jquery。我最近写了一个模块来做到这一点:https://github.com/Tixit/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}]}
    ]
    */
    

    在 odiff 的自述文件中,我列出了我认为不符合我需要的其他模块,如果您也想查看这些模块。

    【讨论】:

      【解决方案6】:

      基于上述解决方案。此版本更正了一个错误,原始解决方案未检测到某些更改。

      getChanges = function (prev, now) {
              var changes = {}, prop, pc;
              for (prop in now) {
                  if (!prev || prev[prop] !== now[prop]) {
                      if (typeof now[prop] == "object") {
                          if (c = getChanges(prev[prop], now[prop]))
                              changes[prop] = c;
                      } else {
                          changes[prop] = now[prop];
                      }
                  }
              }
      
              for (prop in prev) {
                  if (!now || now[prop] !== prev[prop]) {
                      if (typeof prev[prop] == "object") {
                          if (c = getChanges(now[prop], prev[prop]))
                              changes[prop] = c;
                      } else {
                          changes[prop] = prev[prop];
                      }
                  }
              }
      
              return changes;
          };
      

      【讨论】:

        【解决方案7】:

        请注意,如果now[prop] 是日期,typeof now[prop] == "object" 甚至为真。

        而不是: typeof now[prop] == "object"

        我用过: Object.prototype.toString.call( now[prop] === "[object Object]")

        【讨论】:

          猜你喜欢
          • 2017-09-04
          • 2017-03-26
          • 2013-02-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-04
          相关资源
          最近更新 更多