
本文对比了多种在数组头部插入元素的方法,重点分析在不可变数据场景下 concat()、扩展运算符、toSpliced() 等方案的性能表现,并基于真实基准测试推荐最优实践。
本文对比了多种在数组头部插入元素的方法,重点分析在不可变数据场景下 `concat()`、扩展运算符、`tospliced()` 等方案的性能表现,并基于真实基准测试推荐最优实践。
在前端开发中,尤其是使用 Redux、Zustand 或 React 的不可变状态管理时,我们常需在数组开头“添加”一个新元素,同时不修改原数组——即返回一个全新的数组。这种需求看似简单,但不同实现方式的性能差异显著,尤其当数组规模达到数万项时(如消息列表、日志流等场景)。本文将结合实测数据,给出清晰、可落地的技术选型建议。
✅ 推荐方案:优先使用 Array.prototype.concat()
concat() 是目前综合性能最优、兼容性最佳的不可变 prepend 方法:
const newMessages = [{ text: 'new first message' }].concat(messages);
它天然返回新数组,无需手动拷贝源数组(concat() 内部已处理),语义清晰,且在 Chrome 和 Firefox 中均表现稳定。根据 silentmantra/benchmark 的实测(10 万条消息):
-
Chrome 120:
concat()为基准(1.00x),比toSpliced()快约 9%,比for循环快 41%,比扩展运算符(...)快近 4 倍; -
Firefox 120:
toSpliced()略胜(1.00x),但concat()仅慢 5%(1.05x),仍属第一梯队,且兼容性远超toSpliced()(后者需 ES2023+,旧版 Safari/IE 不支持)。
⚠️ 注意:不要画蛇添足地先
slice()或展开原数组再concat——例如[item].concat([...messages])会触发两次拷贝,严重拖慢性能。
✅ 新标准方案:Array.prototype.toSpliced()(现代环境首选)
ES2023 引入的 toSpliced() 是专为不可变数组操作设计的方法,语义最精准:
const newMessages = messages.toSpliced(0, 0, { text: 'new first message' });
// 参数含义:从索引 0 开始,删除 0 个元素,插入新元素
它在 Firefox 中性能最佳,在 Chrome 中紧随 concat() 之后,且未来将成为标准答案。但需注意:
- 兼容性要求:Node.js ≥ 20.12 / Chrome ≥ 114 / Firefox ≥ 115 / Safari ≥ 16.4;
- 若需支持旧环境,务必搭配 polyfill(如
core-js)或降级至concat()。
❌ 不推荐方案及原因
-
扩展运算符(
[item, ...arr]):语法简洁,但性能最差(Chrome 下比concat()慢近 4 倍),因 V8 对其展开逻辑优化不足,且内存分配开销更高; -
unshift()+slice()组合:需先slice()拷贝再unshift(),既冗余又低效(unshift()本身为 O(n) 且会修改临时数组); -
reverse()+push()+reverse():三次遍历,时间复杂度 O(3n),纯属反模式; -
Array.from()手动映射:代码冗长,可读性差,且性能垫底(Chrome 下比concat()慢 16 倍)。
? 实用建议总结
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 最大兼容性(含 IE/旧 Safari) | [item].concat(arr) |
零依赖,性能稳,语义明确 |
| 现代浏览器/Node.js 环境 | arr.toSpliced(0, 0, item) |
标准化、语义最强、性能顶尖 |
| TypeScript 项目 | 两者皆可,建议封装类型安全函数:function prepend<t>(arr: readonly T[], item: T): T[] { return [item].concat(arr); }</t>
|
避免重复书写,统一维护 |
最后提醒:性能优化应基于真实数据规模。若数组通常仅几十项,所有方法差异可忽略;但一旦涉及 >1k 元素的高频 prepend(如实时聊天),选择 concat() 或 toSpliced() 将显著提升响应速度与用户体验。










