阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 合并数组和对象的最佳实践

合并数组和对象的最佳实践

作者:陈川 阅读数:18842人阅读 分类: JavaScript

ECMAScript 6 提供了多种简洁高效的方式来合并数组和对象,这些方法显著提升了代码的可读性和开发效率。从展开运算符到 Object.assign,再到更现代的 Object.entriesArray.prototype 方法,合理选择工具能解决大部分数据合并场景的需求。

合并数组的 ES6 方法

展开运算符(Spread Operator)

展开运算符 ... 是合并数组最直观的方式,支持浅拷贝和快速拼接:

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

支持中间插入元素:

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

Array.prototype.concat()

传统方法在 ES6 中依然有效:

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

与展开运算符的区别在于:

  • concat() 可以接受非数组参数
  • 展开运算符需要所有参数都是可迭代对象

实际应用:去重合并

结合 Set 实现高效去重:

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

合并对象的 ES6 方法

对象展开运算符

类似数组的展开运算,后出现的属性会覆盖前者:

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

支持嵌套合并(浅层):

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

Object.assign()

ES6 标准方法,修改目标对象并返回:

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

与展开运算符的关键差异:

  • 会触发 setters
  • 直接修改第一个参数对象

深度合并方案

通过递归实现深度合并:

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 };
}

特殊场景处理

处理原型链属性

Object.getOwnPropertyDescriptors 保留完整属性描述符:

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

不可枚举属性

使用 Reflect.ownKeys() 捕获所有键:

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

Map/Set 的合并

集合类型需要特殊处理:

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

性能考量

大数据量测试

// 测试10万个元素的数组合并
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

对象合并性能

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

常见陷阱与解决方案

引用保留问题

展开运算符和 Object.assign() 都是浅拷贝:

const original = { a: { b: 1 } };
const copy = { ...original };
copy.a.b = 2;
console.log(original.a.b); // 2 (被意外修改)

解决方案:

const safeCopy = JSON.parse(JSON.stringify(original));
// 或使用库如 lodash.cloneDeep

符号属性处理

ES6 符号属性需要特殊处理:

const sym = Symbol('key');
const obj = { [sym]: 'value' };
const copied = { ...obj }; // 符号属性会被保留

异步数据合并

处理 Promise 对象时需要等待解析:

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

现代浏览器与编译支持

Babel 转译策略

展开运算符会被转换为:

// 输入
const merged = [...arr1, ...arr2];

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

Polyfill 方案

旧环境需要 Object.assign polyfill:

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

与其他特性的结合使用

配合解构赋值

选择性合并属性:

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

动态属性名合并

计算属性名在合并时的表现:

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

TypeScript 中的增强类型

类型安全的合并

使用泛型确保类型一致性:

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

只读属性处理

合并后可能需要移除只读修饰:

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

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌