15

我有一个使用JQuery + Knockout的丰富的基于 AJAX 的 Web 应用程序。我有一个 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 库可能有这样的东西,但我找不到任何看起来像这样工作的东西。

4

7 回答 7

17

我不认为下划线有这样的功能,但是很容易自己实现:

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
}

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

于 2012-06-13T19:52:42.007 回答
3

发布我自己的答案,以便人们可以看到也适用于数组的最终实现。在下面的代码中,“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;
};
于 2013-01-22T22:10:37.370 回答
1

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

它也适用于数组!

于 2014-01-18T02:58:48.800 回答
0

使用 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;
}
于 2015-02-24T14:04:41.110 回答
0

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

于 2015-07-06T20:29:55.633 回答
0

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

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;
    };
于 2016-03-09T15:26:59.437 回答
0

请注意,typeof now[prop] == "object"即使now[prop]是 Date 也是如此。

代替: typeof now[prop] == "object"

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

于 2016-12-07T16:59:40.227 回答