将一个对象合并到另一个对象中的最佳方式(不覆盖)

3

我希望将一个对象 (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的答案。


@loxxy 我不能直接使用 target = result,我需要逐个添加每个属性。 - x13
1个回答

3
您需要对源和目标进行递归属性复制,并检查该属性是否已存在:
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

如果我要合并一个嵌套对象,而且其中一些属性是相同的,那该怎么办呢?我不知道它有多深。 - x13
@ThisNameBetterBeAvailable: 你说的“有一些共同属性”是什么意思? - T.J. Crowder
1
@ThisNameBetterBeAvailable:上面的代码确实合并了所有图层,因此使用了递归。请注意targetprop1获取了prop4,这是一开始没有的。 - T.J. Crowder
这个给我一个错误(*Uncaught RangeError: Maximum call stack size exceeded*),在第12行的if语句后面(if ( val !== null && typeof val === "object" ) {)。很抱歉,我忘了说对象很大。 - x13
1
@ThisNameBetterBeAvailable:如果对象很深,那没关系。听起来可能存在循环引用的问题,这在问题中没有提到。处理循环引用是复杂且应用程序特定的,你必须决定如何处理它。这将涉及维护一个已经遇到的引用列表。 - T.J. Crowder
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接