我正在寻找如何确定何时覆盖或扩展数组/集合的通用解决方案。
假设我们有这样的目标:
let obj1 = {
rolo: 'bar',
yolo: true,
cholo: [1,2,3,4]
};如果我打电话
let obj2 = Object.assign({}, obj1, {
cholo: [5]
});我去拿这个:
obj2 => { rolo: 'bar', yolo: true, cholo: [ 5 ] }但我要找的是:
obj2 => { rolo: 'bar', yolo: true, cholo: [ 1, 2, 3, 4, 5 ] }我能用来扩展数组而不是覆盖的最佳模式是什么?我特别感兴趣的是在库代码的上下文中这样做。其中,用户可以选择是扩展数组还是覆盖数组。
我知道没有简单的模式可以用来做这件事.
例如,我们可以这样做:
override: {
metadata:{
cholo: {
extend: true
}
},
value: {
cholo: [5]
}
}添加一些元数据以配合新对象。然后,我将有一些自定义函数,它不仅调用Object.assign,而且检查元数据属性,并手动复制属性。
就像这样:
let copier = function(defaultObj, {value, metadata}){
let allKeysDupes = Object.keys(value).concat(Object.keys(defaultObj));
let allKeys = allKeysDupes.filter(i => allKeysDupes.indexOf(v === i));
let ret = {};
// manually go through all keys
// check metadata to determine whether to overwrite or extend arrays, etc.
allKeys.forEach(function(k){
if(metadata[k]{
// check the metadata and follow it's rules
}
else if(k in val){
ret[k] = val[k];
}
else {
ret[k] = defaultObj[k];
}
});
return ret;
}发布于 2017-06-25 23:48:12
使用ES6代理的通用解决方案
下面是利用ES6代理 (在ECMAScript 2015语言规范 (ES6)中引入)来对传递的任何普通对象执行可定制的深度合并和连接的通用解决方案。该函数允许为所遇到的每种类型的输入值轻松自定义合并过程,并且应该能够处理任何类型的输入值,只要它传递的对象通常与Object.assign()一起工作。
解释
准确获取类型
对于处理目的来说,容易而准确地识别输入值的类型是很重要的。要做到这一点,该函数利用Object.prototype.toString (对任意值调用时)返回一个沿着[object String]或[object Object]等方向的字符串。在那里,我们可以去掉不必要的位,最后得到一个字符串,比如String或Object等等。
const gettype = e => Object.prototype.toString.call(e).replace(/.*\b(\w+)./, '$1');处理处理程序
我们需要简化不同数据类型的处理。你可以使用混乱的、硬编码的开关语句,但我更喜欢一些更流畅的语句。我喜欢使用函数对象。对于我们想要处理的每个类型,我们只需定义一个与上面定义的gettype函数的输出直接匹配的属性名称。然后,在需要时,我们可以根据相应的类型值查找该函数。
在本例中,我创建了一个默认的处理程序集,然后由函数的第一个参数中提供的处理程序覆盖。
Object.assign(h, {
"Array": (a, b) => [...a, ...b],
"Object": (a, b) => deepmerge(h, a, b),
"Set": (a, b) => new Set([...a, ...b]),
"Map": (a, b) => {
const o = new Map([...a]);
for(let [k,v] of [...b]) {
const vt = gettype(v);
o.set(k, a.has(k) && vt in h ? h[vt](a.get(k), v) : v);
}
return o;
}
}, h);使用代理拦截set操作
现在我们将进入代理部分。首先定义一个"out“对象。这个对象最终将由这个函数返回,并充当代理的目标。
const o = {};接下来,使用set陷阱为"out“对象创建一个代理。当set操作发生时,set陷阱将原始值和新值传递给适当的set处理程序。如果没有适当的set处理程序,则用新值覆盖旧值。确定的值应用于"out“对象。
const p = new Proxy(o, {
set: (o, k, b) => {
const a = o[k];
const at = gettype(a);
return (o[k] = (at in h && k in o ? h[at](a, b) : b), true);
}
});接下来,使用Object.assign将对象合并到代理中,这将触发上面为每个set操作定义的set陷阱。
Object.assign(p, ...args);最后返回"out“对象。
return o;全定义
const deepmerge = (h, ...args) => {
const gettype = e => Object.prototype.toString.call(e).replace(/.*\b(\w+)./, '$1');
const o = {};
Object.assign(h, {
"Array": (a, b) => [...a, ...b],
"Object": (a, b) => deepmerge(h, a, b),
"Set": (a, b) => new Set([...a, ...b]),
"Map": (a, b) => {
const o = new Map([...a]);
for(let [k,v] of [...b]) {
const vt = gettype(v);
o.set(k, a.has(k) && vt in h ? h[vt](a.get(k), v) : v);
}
return o;
}
}, h);
const p = new Proxy(o, {
set: (o, k, b) => {
const a = o[k];
const at = gettype(a);
return (o[k] = (at in h && k in o ? h[at](a, b) : b), true);
}
});
Object.assign(p, ...args);
return o;
};用法
deepmerge(handlers, ...sources)- The name of each property should be the name of the type being handled, with the first letter capitalized.
- The value of each property should be a function which accepts two arguments and returns the result of merging those arguments.
示例
下面的片段显示了两个示例:
const objects = [
{
string: "hello",
number: 1,
boolean: true,
array: [1,2,3],
object: { a: 1, b: 2 },
map: new Map([[1,2],[2,3],[4,2],[undefined, undefined],[null, null]]),
set: new Set([1,2,3]),
null: null,
undefined: undefined
},
{
string: " world",
number: 2,
boolean: false,
array: [4,5,6],
object: { a: 2, b: 1 },
map: new Map([[1,1],[2,0],[3,1],[undefined, null],[null, undefined]]),
set: new Set([4,5,6]),
null: undefined,
undefined: null
}
];
console.log(deepmerge({}, ...objects));
console.log(deepmerge({
"String": (a, b) => a + b,
"Number": (a, b) => a + b,
}, ...objects));<script src="https://cdn.rawgit.com/Tiny-Giant/43cc03adf3cdc84ff935655cbebbe585/raw/754070ca8858efeff5a2c3b8bad6475842565798/deepmerge.js"></script><link rel="stylesheet" href="https://cdn.rawgit.com/Tiny-Giant/f2a53f469863baadf1b4ad48a4b4ea39/raw/b0ede74f374199abe9324334d1c0ef088a850415/deepmerge.css" type="text/css">
发布于 2017-06-24 19:20:15
我采用了这,并根据您的需求进行了修改,引入了一个新的字段concat来指定您是否愿意连接。
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(concat = false, target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
if(concat && Array.isArray(source[key]) && target[key]){
target[key] = source[key].concat(target[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
}
return mergeDeep(concat, target, ...sources);
}
let obj1 = {
rolo: 'bar',
yolo: true,
cholo: [1,2,3,4]
};
let arrObj = {
cholo: [5]
};
console.log(mergeDeep(true, arrObj,{cholo:[2,3,4]}, obj1));
您可以创建一个新对象,然后将新对象的数组与旧对象连接起来。
let obj1 = {
rolo: 'bar',
yolo: true,
cholo: [1,2,3,4]
};
let arrObj = {
cholo: [5]
};
let obj2 = Object.assign({}, obj1, arrObj);
obj2['cholo'] = obj1['cholo'].concat(obj2['cholo']);
console.log(obj2);
发布于 2017-06-24 19:22:03
let obj2 = Object.assign({}, obj1, {
cholo: obj1.cholo.concat([5])
});按照你的建议:
let fn = function({value, metadata}) : Object{
let defaults = obj1;
let ret = {};
// manually go through value
// check metadata to determine whether to overwrite or extend
for (var key in value) {
if (metadata[key] != null) {
if (isArray(value[key]) && isArray(metadata[key]) {
ret[key] = value[key].concat(metadata[key])
} else if (typeof value[key] === 'object' && typeof metadata[key] === 'object') {
ret[key] = Object.assign(value[key], metadata[key]);
} else {
ret[key] = value[key]
}
}
}
return ret;
}https://stackoverflow.com/questions/44739990
复制相似问题