如何深合并而不是浅合并?

IT技术 javascript spread-syntax
2021-01-15 02:09:54

无论Object.assign对象传播只能做一浅合并。

问题的一个例子:

// 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().

有没有办法做到这一点?

6个回答

我知道这是一个老问题,但我能想到的 ES2015/ES6 中最简单的解决方案实际上非常简单,使用 Object.assign(),

希望这有帮助:

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
export function mergeDeep(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 {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

用法示例:

mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});  
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }

你会在下面的答案中找到一个不可变的版本。

请注意,这将导致循环引用的无限递归。如果您认为自己会遇到这个问题,这里有一些关于如何检测循环引用的很好的答案。

...而target[key] = source[key]不是Object.assign(target, { [key]: source[key] });
2021-03-10 02:09:54
如果您的对象图包含会导致无限递归的循环
2021-03-16 02:09:54
item !== null内部不应该需要isObject,因为item在条件开始时已经检查了真实性
2021-03-27 02:09:54
这不支持target. 例如,mergeDeep({a: 3}, {a: {b: 4}})将导致增强的Number对象,这显然是不希望的。此外,isObject不接受数组,但接受任何其他本机对象类型,例如Date不应进行深度复制的 。
2021-03-30 02:09:54
为什么写这个:Object.assign(target, { [key]: {} })如果它可以简单地是target[key] = {}
2021-03-31 02:09:54

您可以使用Lodash 合并

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
嘿人们,这是最简单和最美丽的解决方案。Lodash 很棒,他们应该将它包含为核心 js 对象
2021-03-14 02:09:54
结果不应该{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }吗?
2021-03-17 02:09:54
我更喜欢这个,它压缩了 6 倍。
2021-04-05 02:09:54
结果{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }是正确的,因为我们正在合并数组的元素。该元件0object.a就是{b: 2},元件0other.a{c: 3}当这两个因具有相同的数组索引而合并时,结果为{ 'b': 2, 'c': 3 },即0新对象中的元素
2021-04-07 02:09:54
@J.Hesters 要实现您所描述的内容,lodash 还提供了另一种方法:mergeWith
2021-04-08 02:09:54

当涉及到宿主对象或任何比一袋值更复杂的对象时,这个问题并不重要

  • 您是调用 getter 来获取值还是复制属性描述符?
  • 如果合并目标有一个 setter(要么是自己的属性要么在它的原型链中)怎么办?你认为这个值已经存在还是调用 setter 来更新当前值?
  • 你是调用自有属性函数还是复制它们?如果它们是绑定函数或箭头函数,取决于它们在定义时的作用域链中的某些内容怎么办?
  • 如果它类似于 DOM 节点呢?你当然不想把它当作简单的对象,只是将它的所有属性深度合并到
  • 如何处理像数组、映射或集合这样的“简单”结构?考虑它们已经存在还是也合并它们?
  • 如何处理不可枚举的自身属性?
  • 新的子树呢?简单地通过引用分配还是深度克隆?
  • 如何处理冻结/密封/不可扩展的对象?

要记住的另一件事:包含循环的对象图。它通常不难处理——只需保留一个Set已经访问过的源对象——但经常被遗忘。

您可能应该编写一个深度合并函数,它只需要原始值和简单对象——最多是结构化克隆算法可以处理的那些类型——作为合并源。如果遇到任何它无法处理的东西或只是通过引用分配而不是深度合并,则抛出。

换句话说,没有一刀切的算法,您要么必须推出自己的算法,要么寻找恰好涵盖您的用例的库方法。

您提出了许多很好的问题,我很想看到您的建议得到实施。所以我试着在下面做一个。你能看看并评论吗?stackoverflow.com/a/48579540/8122487
2021-03-11 02:09:54
V8 开发人员不实施安全“文档状态”传输的借口
2021-03-18 02:09:54

这是@Salakar 答案的不可变(不修改输入)版本。如果您正在做函数式编程类型的东西,则很有用。

export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

export default function mergeDeep(target, source) {
  let output = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!(key in target))
          Object.assign(output, { [key]: source[key] });
        else
          output[key] = mergeDeep(target[key], source[key]);
      } else {
        Object.assign(output, { [key]: source[key] });
      }
    });
  }
  return output;
}
@torazaburo 请参阅我之前发布的关于 isObject 函数的帖子
2021-03-11 02:09:54
更新了它。经过一些测试后,我发现了一个深度嵌套对象的错误
2021-03-25 02:09:54
isObject你没有需要检查&& item !== null的结束,因为与线开始item &&,不是吗?
2021-03-25 02:09:54
如果 source 嵌套的子对象比 target 更深,这些对象仍将引用mergedDeep's 输出中的相同值(我认为)。例如, const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source); merged.b.c; // 2 source.b.c = 3; merged.b.c; // 3 这是一个问题吗?它不会改变输入,但是对输入的任何未来突变都可能使输出发生突变,反之亦然,如果发生突变以输出突变输入。不过,就其value而言,ramdaR.merge()具有相同的行为。
2021-03-28 02:09:54
它是一个计算的属性名,第一个将使用的值key作为属性名,后一个将“key”作为属性名。请参阅:es6-features.org/#ComputedPropertyNames
2021-04-09 02:09:54

由于这个问题仍然存在,这是另一种方法:

  • ES6/2015
  • 不可变(不修改原始对象)
  • 处理数组(连接它们)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);

要使数组唯一,您可以更改 prev[key] = pVal.concat(...oVal);prev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
2021-03-16 02:09:54
独特数组的替代 es6 解决方案。更改prev[key] = pVal.concat(...oVal);prev[key] = [...new Set([...oVal, ...pVal])];参考:stackoverflow.com/a/9229821/6671505
2021-03-18 02:09:54
辉煌。这个也证明了数组被合并了,这就是我一直在寻找的。
2021-03-30 02:09:54
是的,@CplLL 解决方案据说是不可变的,但在函数内部使用了实际的对象可变性,而 usingreduce 不可变。
2021-04-02 02:09:54
这很好。但是,当我们有包含重复元素的数组时,这些元素会被连接起来(有重复的元素)。我对此进行了调整以采用参数(数组唯一:true/false)。
2021-04-09 02:09:54