两个对象。assign和Object spread只做浅合并。

这个问题的一个例子:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }

输出是您所期望的。然而,如果我尝试这样做:

// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

而不是

{ a: { a: 1, b: 1 } }

你得到

{ a: { b: 1 } }

X被完全覆盖,因为扩展语法只覆盖了一层。这与Object.assign()相同。

有办法做到这一点吗?


当前回答

ES5的一个简单解决方案(覆盖现有值):

function merge(current, update) { Object.keys(update).forEach(function(key) { // if update[key] exist, and it's not a string or array, // we go in one level deeper if (current.hasOwnProperty(key) && typeof current[key] === 'object' && !(current[key] instanceof Array)) { merge(current[key], update[key]); // if update[key] doesn't exist in current, or it's a string // or array, then assign/overwrite current[key] to update[key] } else { current[key] = update[key]; } }); return current; } var x = { a: { a: 1 } } var y = { a: { b: 1 } } console.log(merge(x, y));

其他回答

Ramda是一个很好的javascript函数库,它有mergeDeepLeft和mergeDeepRight。这些方法都能解决这个问题。请在这里查看文档:https://ramdajs.com/docs/#mergeDeepLeft

对于问题中的具体例子,我们可以使用:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}

我把这里所有的答案都看了一遍,然后拼凑出了一个我自己的答案。现有的大多数答案都不是我想要的方式。

这对于2021年来说是相当可怕的,所以任何改善的建议,我都洗耳恭听!

这是在Typescript中

type Props = Record<string, any>

export const deepMerge = (target: Props, ...sources: Props[]): Props => {
  if (!sources.length) {
    return target
  }

  Object.entries(sources.shift() ?? []).forEach(([key, value]) => {
    if (!target[key]) {
      Object.assign(target, { [key]: {} })
    }

    if (
      value.constructor === Object ||
      (value.constructor === Array && value.find(v => v.constructor === Object))
    ) {
      deepMerge(target[key], value)
    } else if (value.constructor === Array) {
      Object.assign(target, {
        [key]: value.find(v => v.constructor === Array)
          ? target[key].concat(value)
          : [...new Set([...target[key], ...value])],
      })
    } else {
      Object.assign(target, { [key]: value })
    }
  })

  return target
}

平面数组使用[…]删除重复值。新的(…)]。

嵌套数组使用concat连接。

如果你正在使用ImmutableJS,你可以使用mergeDeep:

fromJS(options).mergeDeep(options2).toJS();

如果您想合并多个普通对象(不要修改输入对象)。基于对象。分配polyfill

function isPlainObject(a) { return (!!a) && (a.constructor === Object); } function merge(target) { let to = Object.assign({}, target); for (let index = 1; index < arguments.length; index++) { let nextSource = arguments[index]; if (nextSource !== null && nextSource !== undefined) { for (let nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { if (isPlainObject(to[nextKey]) && isPlainObject(nextSource[nextKey])) { to[nextKey] = merge(to[nextKey], nextSource[nextKey]); } else { to[nextKey] = nextSource[nextKey]; } } } } } return to; } // Usage var obj1 = { a: 1, b: { x: 2, y: { t: 3, u: 4 } }, c: "hi" }; var obj2 = { b: { x: 200, y: { u: 4000, v: 5000 } } }; var obj3 = { c: "hello" }; console.log("result", merge(obj1, obj2, obj3)); console.log("obj1", obj1); console.log("obj2", obj2); console.log("obj3", obj3);

如果你想合并有限的深度

function isPlainObject(a) { return (!!a) && (a.constructor === Object); } function merge(target) { let to = Object.assign({}, target); const hasDepth = arguments.length > 2 && typeof arguments[arguments.length - 1] === 'number'; const depth = hasDepth ? arguments[arguments.length - 1] : Infinity; const lastObjectIndex = hasDepth ? arguments.length - 2 : arguments.length - 1; for (let index = 1; index <= lastObjectIndex; index++) { let nextSource = arguments[index]; if (nextSource !== null && nextSource !== undefined) { for (let nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { if (depth > 0 && isPlainObject(to[nextKey]) && isPlainObject(nextSource[nextKey])) { to[nextKey] = merge(to[nextKey], nextSource[nextKey], depth - 1); } else { to[nextKey] = nextSource[nextKey]; } } } } } return to; } // Usage var obj1 = { a: 1, b: { x: 2, y: { t: 3, u: 4, z: {zzz: 100} } }, c: "hi" }; var obj2 = { b: { y: { u: 4000, v: 5000, z: {} } } }; var obj3 = { c: "hello" }; console.log('deep 0', merge(obj1, obj2, obj3, 0)); console.log('deep 1', merge(obj1, obj2, obj3, 1)); console.log('deep 2', merge(obj1, obj2, obj3, 2)); console.log('deep 2', merge(obj1, obj2, obj3, 4));

许多答案使用数十行代码,或者需要向项目添加一个新库,但如果您使用递归,这只是4行代码。

函数合并(当前,更新){ for (Object.keys(updates)的key) { if (!current. hasownproperty (key) || typeof updates[key] !== 'object') current[key] = updates[key]; Else merge(current[key], updates[key]); } 返回当前; } console.log(合并({答:{:1}},{:{b: 1}}));

数组处理:上面的版本用新值覆盖旧的数组值。如果你想保留旧的数组值并添加新的,只需在else语句上方添加一个else If (current[key] instanceof array && updates[key] instanceof array) current[key] = current[key].concat(updates[key])块,你就都设置好了。