我想将一个对象(parent
)合并到另一个对象(window
)而不覆盖现有值。
两个对象都不知道键,值和长度,但我可以放心地假设会有嵌套对象
我无法重新创建目标对象,原因是它需要是一个实际的合并。
在javascript中执行此操作的最佳方式是什么?
var target = {
prop1: {
prop1stuff1: 42,
prop3: 15.5
},
42: 'stuff'
};
var source = {
prop1: {
prop1stuff1: 42,
prop4: 17,
prop3: 18
},
'42': 'test'
};
function merge(t, s){
//code to merge source (s) into target (t)
//without overwriting existing values
//and without overwriting t with a new object
}
merge(target, source); //alter target by reference, does not return anything
console.log(target);
// ^ this outputs:
{
prop1: {
prop1stuff1: 42,
prop3: 15.5,
prop4: 17
},
42: 'stuff'
}
修改
我无法将新对象分配给目标,我必须逐个添加属性 我也不知道嵌套对象有多深
*第二次编辑:***
TJ Crowder的答案有效但我试图合并的对象包含很多循环引用,导致无限循环。
我添加了一个循环参考探测器,我现在要更新TJ Crowder的答案。
答案 0 :(得分:2)
您要从源到目标执行属性的递归副本,并检查以确保该属性尚不存在:
function merge(t, s){
// Do nothing if they're the same object
if (t === s) {
return;
}
// Loop through source's own enumerable properties
Object.keys(s).forEach(function(key) {
// Get the value
var val = s[key];
// Is it a non-null object reference?
if (val !== null && typeof val === "object") {
// Yes, if it doesn't exist yet on target, create it
if (!t.hasOwnProperty(key)) {
t[key] = {};
}
// Recurse into that object
merge(t[key], s[key]);
// Not a non-null object ref, copy if target doesn't have it
} else if (!t.hasOwnProperty(key)) {
t[key] = s[key];
}
});
}
注意:
以上假设源中的任何对象都是普通对象,因此如果目标中不存在,我们使用{}
创建它。这不是很复杂,我们可能想要更进一步,例如检查它是否是一个数组,或其他内置类型,并做更广泛的事情。但上面的内容应该让你开始。
我们正在做“自己的”属性;您可以使用for-in
循环而不是Object.keys
来执行属性,包括从原型继承的属性;然后您使用if (!(key in t))
代替!t.hasOwnProperty(key)
。
示例:
var common = {
commonProp: "I'm a prop on an object both target and source have"
};
var target = {
prop1: {
prop1stuff1: 42,
prop3: 15.5
},
42: 'stuff',
common: common
};
var source = {
prop1: {
prop1stuff1: 42,
prop4: 17,
prop3: 18
},
'42': 'test',
common: common
};
function merge(t, s){
// Do nothing if they're the same object
if (t === s) {
return;
}
// Loop through source's own enumerable properties
Object.keys(s).forEach(function(key) {
// Get the value
var val = s[key];
// Is it a non-null object reference?
if (val !== null && typeof val === "object") {
// Yes, if it doesn't exist yet on target, create it
if (!t.hasOwnProperty(key)) {
t[key] = {};
}
// Recurse into that object
merge(t[key], s[key]);
// Not a non-null object ref, copy if target doesn't have it
} else if (!t.hasOwnProperty(key)) {
t[key] = s[key];
}
});
}
merge(target, source);
document.body.innerHTML =
"<pre>" + JSON.stringify(target) + "</pre>";
OP扩展了上述内容,以便为其目的充分处理循环引用(可能不是通用的):
function merge(t, s){
// Do nothing if they're the same object
if (t === s) return;
// Loop through source's own enumerable properties
Object.keys(s).forEach(function(key) {
// Get the value
var val = s[key];
// Is it a non-null object reference?
if (val !== null && typeof val === "object") {
// Yes, if it doesn't exist yet on target, create it
if (!t.hasOwnProperty(key)) t[key] = {};
// Recurse into that object IF IT DOES NOT CONTAIN CIRCULAR REFERENCES
if ( !isCyclic( t[ key ] ) && !isCyclic( s[ key ] ) ) merge( t[ key ], s[ key ] );
// Not a non-null object ref, copy if target doesn't have it
} else if (!t.hasOwnProperty(key)) t[key] = s[key];
});
function isCyclic( obj ) {
var seenObjects = [];
function detect( obj ) {
if ( obj && typeof obj === 'object' ) {
if ( seenObjects.indexOf( obj ) !== -1 ) return true;
seenObjects.push( obj );
for ( var key in obj ) if ( obj.hasOwnProperty( key ) && detect( obj[ key ] ) ) return true;
}
return false;
}
return detect( obj );
}
//and now... Merge!
merge( window, parent );
//window now has all properties of parent
//that it didn't have before