阿里云主机折上折
  • 微信号
Current Site:Index > Best practices for merging arrays and objects

Best practices for merging arrays and objects

Author:Chuan Chen 阅读数:18841人阅读 分类: JavaScript

ECMAScript 6 provides multiple concise and efficient ways to merge arrays and objects, significantly improving code readability and development efficiency. From the spread operator to Object.assign, and to more modern methods like Object.entries and Array.prototype methods, choosing the right tool can address most data merging scenarios.

ES6 Methods for Merging Arrays

Spread Operator

The spread operator ... is the most intuitive way to merge arrays, supporting shallow copying and quick concatenation:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

Supports inserting elements in between:

const withGap = [...arr1, 'a', ...arr2]; // [1, 2, 3, 'a', 4, 5, 6]

Array.prototype.concat()

The traditional method remains valid in ES6:

const merged = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]

Key differences from the spread operator:

  • concat() can accept non-array arguments.
  • The spread operator requires all arguments to be iterable.

Practical Application: Deduplication Merge

Combine with Set for efficient deduplication:

const uniqueMerge = [...new Set([...arr1, ...arr2])];

ES6 Methods for Merging Objects

Object Spread Operator

Similar to array spreading, later properties override earlier ones:

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }

Supports shallow nested merging:

const withNested = { ...obj1, nested: { ...obj2 } };

Object.assign()

The ES6 standard method modifies and returns the target object:

const merged = Object.assign({}, obj1, obj2);

Key differences from the spread operator:

  • Triggers setters.
  • Directly modifies the first argument object.

Deep Merge Solution

Implement deep merging via recursion:

function deepMerge(target, source) {
  for (const key in source) {
    if (source[key] instanceof Object && target[key]) {
      Object.assign(source[key], deepMerge(target[key], source[key]));
    }
  }
  return { ...target, ...source };
}

Handling Special Scenarios

Prototype Chain Properties

Use Object.getOwnPropertyDescriptors to preserve complete property descriptors:

const completeCopy = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Non-enumerable Properties

Use Reflect.ownKeys() to capture all keys:

const allKeysObj = {};
Reflect.ownKeys(obj).forEach(key => {
  allKeysObj[key] = obj[key];
});

Merging Map/Set

Special handling required for collection types:

const mergedMap = new Map([...map1, ...map2]);
const mergedSet = new Set([...set1, ...set2]);

Performance Considerations

Large Data Volume Testing

// Test merging arrays with 100,000 elements each
const bigArr1 = Array(1e5).fill(0);
const bigArr2 = Array(1e5).fill(1);

console.time('spread');
const r1 = [...bigArr1, ...bigArr2];
console.timeEnd('spread'); // ~15ms

console.time('concat');
const r2 = bigArr1.concat(bigArr2);
console.timeEnd('concat'); // ~5ms

Object Merge Performance

const bigObj1 = { ...Array(1e5).fill(0).map((_,i) => ({[i]:i})) };
const bigObj2 = { ...Array(1e5).fill(0).map((_,i) => ({[i+1e5]:i})) };

console.time('objectSpread');
const o1 = { ...bigObj1, ...bigObj2 };
console.timeEnd('objectSpread'); // ~120ms

console.time('objectAssign');
const o2 = Object.assign({}, bigObj1, bigObj2);
console.timeEnd('objectAssign'); // ~110ms

Common Pitfalls and Solutions

Reference Retention Issue

Both the spread operator and Object.assign() perform shallow copies:

const original = { a: { b: 1 } };
const copy = { ...original };
copy.a.b = 2;
console.log(original.a.b); // 2 (unintentionally modified)

Solution:

const safeCopy = JSON.parse(JSON.stringify(original));
// Or use libraries like lodash.cloneDeep

Symbol Property Handling

ES6 symbol properties require special handling:

const sym = Symbol('key');
const obj = { [sym]: 'value' };
const copied = { ...obj }; // Symbol properties are retained

Asynchronous Data Merging

Handling Promise objects requires awaiting resolution:

async function mergeAsyncObjects(obj1Promise, obj2Promise) {
  const [obj1, obj2] = await Promise.all([obj1Promise, obj2Promise]);
  return { ...obj1, ...obj2 };
}

Modern Browser and Compilation Support

Babel Transpilation Strategy

The spread operator is transpiled to:

// Input
const merged = [...arr1, ...arr2];

// Output
var merged = [].concat(arr1, arr2);

Polyfill Solution

Legacy environments require Object.assign polyfill:

if (typeof Object.assign != 'function') {
  Object.assign = function(target) {
    // ...polyfill implementation
  };
}

Integration with Other Features

Combined with Destructuring

Selective property merging:

const { a, ...rest } = obj1;
const merged = { a: a || obj2.a, ...rest, ...obj2 };

Dynamic Property Name Merging

Behavior of computed property names during merging:

const dynamicKey = 'custom';
const merged = { 
  ...obj1,
  [dynamicKey + 'Prop']: 'value' 
};

Enhanced Types in TypeScript

Type-Safe Merging

Use generics to ensure type consistency:

function safeMerge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b };
}

Readonly Property Handling

Readonly modifiers may need removal after merging:

type Writeable<T> = { -readonly [P in keyof T]: T[P] };
const merged: Writeable<T> = { ...obj1, ...obj2 };

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.