展开运算符语法简洁,适用于数组对象复制合并,但性能随数据量增加而下降,因每次调用均需遍历元素生成新对象,时间复杂度O(n),且无法中断遍历,内存占用高;相较传统方法如push.apply、Object.assign或concat,其在大数据量或高频操作中效率较低,易触发栈溢出或加重GC负担;建议小数据场景使用以提升可读性,性能关键路径则改用原生优化方法或分片处理,权衡简洁性与执行效率。

展开运算符(…)在JavaScript中广泛用于数组和对象的复制、合并等操作,写法简洁,可读性强。但它的性能表现因使用场景不同而有差异,尤其在处理大量数据时需要特别注意。
展开运算符的基本用途
展开运算符可以将可迭代对象(如数组、字符串、类数组)展开为独立元素,常用于以下场景:
- 数组复制:const newArr = […oldArr];
- 数组合并:const merged = […arr1, …arr2];
- 函数参数传递:myFunc(…args);
- 对象浅拷贝与合并:const newObj = { …obj }; 或 { …obj1, …obj2 };
性能瓶颈分析
尽管语法优雅,展开运算符在某些情况下会带来性能开销,主要体现在以下几个方面:
- 创建新对象/数组:每次使用展开运算符都会生成新的引用,底层需要遍历所有属性或元素并逐个赋值,时间复杂度为 O(n)。对于大数组或深层对象,这种操作成本显著增加。
- 不可中断的遍历过程:展开是同步且完整执行的操作,无法像 for…of 那样中途跳出或惰性求值,导致在不需要全部元素的场景下仍完成全量复制。
- 内存占用上升:频繁使用展开可能导致短时间内产生大量临时对象,加重垃圾回收压力,尤其在循环或高频调用函数中。
- 对象属性枚举顺序限制:对象展开依赖于属性的枚举顺序(按规范定义),在不同引擎中可能存在细微差异,且不保证 symbol 属性的统一处理。
与传统方法的对比
在关键路径上,替代方案往往更高效:
立即学习“Java免费学习笔记(深入)”;
- 数组合并:
- 展开方式:const c = […a, …b]; —— 简洁但需构建新数组
- 推荐方式:a.push.apply(a, b); 或 a.push(…b);(若 b 不大)—— 原地修改,避免中间结构
- 浅拷贝:
- 展开:{…obj} —— 支持枚举属性,兼容性好
- Object.assign():Object.assign({}, obj) —— 性能略优,尤其是多源对象时
- 函数传参:
优化建议
为了兼顾代码可读性和运行效率,可参考以下实践:
- 小数据量场景下放心使用展开运算符,提升开发体验
- 在性能敏感路径(如渲染循环、高频事件处理)避免滥用展开,特别是嵌套展开或连续合并
- 考虑使用 slice()、concat() 等原生方法替代部分展开操作,它们通常经过引擎深度优化
- 对大型对象或数组,优先采用结构性共享(如 Immutable.js)或分片处理策略
- 必要时通过 console.time() 对比实际执行耗时,以真实数据驱动决策
基本上就这些。展开运算符是个好工具,但不能忽视其背后的代价。理解它的工作机制,才能在简洁与性能之间做出合理权衡。


