Функция jQuery для вычисления разницы между двумя объектами JavaScript
У меня есть богатое веб-приложение на основе AJAX, которое использует JQuery + нокаут. У меня есть плагин JQuery, который обертывает мои модели представления нокаутом, чтобы выставлять полезные методы, такие как .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 может иметь что-то подобное, но я не мог найти ничего похожего на это.
Ответы
Ответ 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
}
Это не будет работать с массивами (или любыми другими не-равными объектами) или иначе структурированными объектами (удаление, примитив к изменениям типа объекта).
Ответ 2
Опубликуйте свой собственный ответ, чтобы люди могли увидеть окончательную реализацию, которая также работает с массивами. В приведенном ниже коде "um" - это мое пространство имен, и я также использую методы _.isArray() и _.isObject из Underscore.js.
Код, который ищет "_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] равен undefined (в результате теперь [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;
}
Ответ 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}]}
]
*/
В readme для 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
Помните, что typeof now[prop] == "object"
равно true, если now[prop]
- дата.
Вместо:
typeof now[prop] == "object"
Я использовал:
Object.prototype.toString.call( now[prop] === "[object Object]")