jQuery函数,用于计算两个JavaScript对象之间的差异

jQuery function to compute the difference between two JavaScript objects

本文关键字:对象 JavaScript 两个 之间 函数 用于 计算 jQuery      更新时间:2023-09-26

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

我有一个名为 .setBaseline() 的方法,它基本上是在填充数据模型后(通过映射插件)拍摄数据的快照。然后,我可以使用此快照快速确定模型是否已更改。

我正在寻找的是某种通用函数,它可以返回一个对象,该对象表示两个 2 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 库可能有这样的东西,但我找不到任何似乎像这样工作的东西。

我不认为下划线中有这样的函数,但很容易实现自己:

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
}

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

发布我自己的答案,以便人们可以看到也适用于数组的最终实现。在下面的代码中,"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;
};

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

它也适用于数组!

使用

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

不需要为此使用 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 的自述文件中,我列出了其他模块,如果您也想查看这些模块,我确定这些模块不符合我的需求。

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

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;
    };
请注意

,如果now[prop]是日期,则typeof now[prop] == "object"为真。

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

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