合并数组和对象的最佳实践
ECMAScript 6 提供了多种简洁高效的方式来合并数组和对象,这些方法显著提升了代码的可读性和开发效率。从展开运算符到 Object.assign
,再到更现代的 Object.entries
和 Array.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
下一篇:展开运算符的性能考虑