要解释一下,请在下面的对象中进行更改:
obj = {'a': 1, 'b': 2} // Version 1
obj['a'] = 2 // Version 2
obj['c'] = 3 // Version 3
我希望能够获得该对象的任何这些版本,例如从版本2开始获取obj
。我不希望每次想要更新单个密钥时都存储整个对象的副本。
如何实现此功能?
我正在尝试使用的实际对象有大约500,000个键。这就是为什么我不希望每次更新都存储整个副本。我理解这个理论解决方案的首选语言是python
或javascript
,但我会采取任何措施。
答案 0 :(得分:9)
您可以使用ES6代理。这些操作会捕获对象的任何读/写操作,并记录更改日志中的每个更改,这些更改可用于前后滚动更改。
下面是一个基本实现,如果您打算在对象上应用基本更新操作以外的其他功能,则可能需要更多功能。它允许获取当前版本号并将对象移回(或转发)到特定版本。每当您对对象进行更改时,它都会首先移至其最新版本。
此代码段显示了一些操作,例如更改字符串属性,添加到数组以及移动数组,同时前后移动到其他版本。
编辑现在它还可以将更改日志作为对象,并将更改日志应用于初始对象。这样,您可以保存初始对象和更改日志的JSON,并重放更改以获取最终对象。
function VersionControlled(obj, changeLog = []) {
var targets = [], version = 0, savedLength,
hash = new Map([[obj, []]]),
handler = {
get: function(target, property) {
var x = target[property];
if (Object(x) !== x) return x;
hash.set(x, hash.get(target).concat(property));
return new Proxy(x, handler);
},
set: update,
deleteProperty: update
};
function gotoVersion(newVersion) {
newVersion = Math.max(0, Math.min(changeLog.length, newVersion));
var chg, target, path, property,
val = newVersion > version ? 'newValue' : 'oldValue';
while (version !== newVersion) {
if (version > newVersion) version--;
chg = changeLog[version];
path = chg.path.slice();
property = path.pop();
target = targets[version] ||
(targets[version] = path.reduce ( (o, p) => o[p], obj ));
if (chg.hasOwnProperty(val)) {
target[property] = chg[val];
} else {
delete target[property];
}
if (version < newVersion) version++;
}
return true;
}
function gotoLastVersion() {
return gotoVersion(changeLog.length);
}
function update(target, property, value) {
gotoLastVersion(); // only last version can be modified
var change = {path: hash.get(target).concat([property])};
if (arguments.length > 2) change.newValue = value;
// Some care concerning the length property of arrays:
if (Array.isArray(target) && +property >= target.length) {
savedLength = target.length;
}
if (property in target) {
if (property === 'length' && savedLength !== undefined) {
change.oldValue = savedLength;
savedLength = undefined;
} else {
change.oldValue = target[property];
}
}
changeLog.push(change);
targets.push(target);
return gotoLastVersion();
}
this.data = new Proxy(obj, handler);
this.getVersion = _ => version;
this.gotoVersion = gotoVersion;
this.gotoLastVersion = gotoLastVersion;
this.getChangeLog = _ => changeLog;
// apply change log
gotoLastVersion();
}
// sample data
var obj = { list: [1, { p: 'hello' }, 3] };
// Get versioning object for it
var vc = new VersionControlled(obj);
obj = vc.data; // we don't need the original anymore, this one looks the same
// Demo of actions:
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Change text:`);
obj.list[1].p = 'bye';
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Bookmark & add property:`);
var bookmark = vc.getVersion();
obj.list[1].q = ['added'];
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Push on list, then shift:`);
obj.list.push(4); // changes both length and index '4' property => 2 version increments
obj.list.shift(); // several changes and a deletion
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Go to bookmark:`);
vc.gotoVersion(bookmark);
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Go to last version:`);
vc.gotoLastVersion();
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}. Get change log:`);
var changeLog = vc.getChangeLog();
for (var chg of changeLog) {
console.log(JSON.stringify(chg));
}
console.log('Restart from scratch, and apply the change log:');
obj = { list: [1, { p: 'hello' }, 3] };
vc = new VersionControlled(obj, changeLog);
obj = vc.data;
console.log(`v${vc.getVersion()} ${JSON.stringify(obj)}`);
.as-console-wrapper { max-height: 100% !important; top: 0; }
答案 1 :(得分:0)
您不需要保存整个对象。
只是差异。对于每个版本。
此函数将使用lodash进行深度比较,并返回旧对象和新对象之间的差异。
var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
if (!_.isEqual(obj1[key] !== obj2[key])) {
result[key] = {obj1: obj1[key], obj2: obj2[key]}
}
return result;
}, {});
你需要保留第一个对象,但我认为你可以保留这种版本。
答案 2 :(得分:0)
改为使用Map对象:
const obj = new Map( [
['name', 'bob'],['number',2]
])
const v2 = ( new Map( obj ) ).set('name','suzie')
const v3 = ( new Map( v2 ) ).set('value',3)
// obj is 'name'->'bob', 'number'->2
// v2 is 'name'->'suzie', 'number'->2
// v3 is 'name'->'suzie', 'number'->2, 'value'->3
新的Map(obj)是一个克隆,但是数据本身不会被复制或突变,因此非常适合版本控制,保存要撤消的操作列表等。