从另一个对象更新JavaScript对象属性

47

我想要更新一个对象,它可能长这样:

currentObject = {
    someValue : "value",
    myObject : {
        attribute1 : "foo",
        attribute2 : "bar"
    }
};

使用包含一些更改的对象,例如:

updateObject = {
    myObject : {
        attribute2 : "hello world"
    }
};

最后,我希望currentObject被更新,以便:

currentObject.myObject.attribute2 == "hello world"
那应该也可以对其他对象实现。 作为解决方案,我考虑遍历对象并以某种方式处理名称空间。但我想知道是否可以使用类似jQuery或Prototype的库轻松解决这个问题。

如果你使用jQuery,那么$.extend应该可以满足你的需求。 - gen_Eric
@RocketHazmat:不,它不是递归的。 - Bergi
1
@Bergi:如果你将第一个参数传递为“true”,那就是这样的!;-) - gen_Eric
@Bergi 在你对某个事物发表言论之前,请先查阅相关资料。这里有一个链接,你可以了解一下.extend的相关内容:http://api.jquery.com/jQuery.extend/ - Ian
@RocketHazmat:没错,我总是忘记这一点(因为它的数组处理方式让我不太喜欢这个函数)。 - Bergi
4个回答

27

我建议使用underscore.js(或更好的lo-dash)extend

_.extend(destination, *sources)

将所有源对象的属性复制到目标对象中,并返回目标对象。顺序很重要,因此最后一个源将覆盖之前参数中相同名称的属性

_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}

7
据我所见,Underscore和Lodash的extend都会覆盖myObject属性,而不是简单地更新它(即它将仅包含attribute2)。然而,Lodash的merge可以解决这个问题。 - TataBlack
1
正如@TataBlack所指出的那样,给定var a = {name: 'moe', age: 50}, b = {age: 30}; var c = _.merge({}, a, b);c将是{name: 'moe', age: 30},而ab保持不变。 - Season

14
function update(obj/*, …*/) {
    for (var i=1; i<arguments.length; i++) {
        for (var prop in arguments[i]) {
            var val = arguments[i][prop];
            if (typeof val == "object") // this also applies to arrays or null!
                update(obj[prop], val);
            else
                obj[prop] = val;
        }
    }
    return obj;
}

这个应该可以解决问题:update(currentObject, updateObject)。你可能需要添加一些类型检查,比如 Object(obj) === obj 来仅将真正的对象扩展到真正的对象,对于数组使用正确的循环或 hasOwnProperty 测试。


4
这是一个关于 Object.keys 和递归的例子:
// execute object update function
update(currentObject, updateObject)

// instantiate object update function
function update (targetObject, obj) {
  Object.keys(obj).forEach(function (key) {

    // delete property if set to undefined or null
    if ( undefined === obj[key] || null === obj[key] ) {
      delete targetObject[key]
    }

    // property value is object, so recurse
    else if ( 
        'object' === typeof obj[key] 
        && !Array.isArray(obj[key]) 
    ) {

      // target property not object, overwrite with empty object
      if ( 
        !('object' === typeof targetObject[key] 
        && !Array.isArray(targetObject[key])) 
      ) {
        targetObject[key] = {}
      }

      // recurse
      update(targetObject[key], obj[key])
    }

    // set target property to update property
    else {
      targetObject[key] = obj[key]
    }
  })
}

JSFiddle演示(打开控制台)。


1
一个简单的实现看起来像这样。
function copyInto(target /*, source1, sourcen */) {
    if (!target || typeof target !== "object")
        target = {};

    if (arguments.length < 2)
        return target;

    for (var len = arguments.length - 1; len > 0; len--)
        cloneObject(arguments[len-1], arguments[len]);

    return target;
}

function cloneObject(target, source) {
    if (!source || !target || typeof source !== "object" || typeof target !== "object")
        throw new TypeError("Invalid argument");

    for (var p in source)
        if (source.hasOwnProperty(p))
            if (source[p] && typeof source[p] === "object")
                if (target[p] && typeof target[p] === "object")
                    cloneObject(target[p], source[p]);
                else
                    target[p] = source[p];
            else 
                target[p] = source[p];
}

这假设不应克隆任何继承属性。它也不检查诸如DOM对象或装箱基元之类的内容。
我们需要反向迭代参数,以便复制是从右到左进行的。
然后,我们制作一个单独的cloneObject函数,以处理嵌套对象的递归复制,方式不会干扰原始对象参数的从右到左的复制。
它还确保初始目标是一个普通对象。
如果传递给它的不是对象,cloneObject函数将抛出错误。

为什么要从源到源再到目标进行复制? - Bergi
@Bergi:我想采用不同的方法,但我认为我应该通过创建中间对象而不是修改原始对象来使其非破坏性。此外,如果存在{foo:[...]} <- {foo:{...}}的情况,我真的应该区分数组和对象。 - user1689386
是的,它应该对源文件无破坏性-但你不需要中间对象,只要使用 cloneInto(target)。同时,用数组扩展普通对象确实很麻烦 :-) - Bergi

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