☰
jstips 第 00 期:向数组中插入元素——尾部、头部、中间三种场景的最优写法与性能实测
2026/10/8 18:21:16 网站建设 项目流程
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

在 JavaScript 日常开发中,向已有数组中插入元素是出现频率极高的操作:向尾部追加用push,向头部插入用unshift,向中间写入用splice。但这三个"众所周知"的方法并非在每种场景下都是最高效的选择——通过下标直接赋值、借助concat生成新数组,往往能获得显著更优的性能。本文以 jstips 项目(一个收录短小实用 JavaScript 技巧的开源仓库)的第 00 期 tip Insert item inside an Array 为主体,完整复现其在移动端与桌面端多个浏览器上的基准测试数据,并结合数组方法的语义差异(原地修改 vs. 返回新数组),帮助你在真实项目中选出兼顾性能与副作用行为的插入方案。

一张表看懂:插入位置与候选 API

原文档开篇即给出结论:向数组尾部插入用push,头部用unshift,中间用splice。但进一步对比后,每种场景还有替代写法:

插入位置原地修改原数组返回新数组
尾部arr.push(item)/arr[arr.length] = itemarr.concat([item])
头部arr.unshift(item)[item].concat(arr)
中间arr.splice(index, 0, item)无(splice即原地操作)

其中arr[arr.length] = item与[item].concat(arr)正是原文档重点推荐的"更高效的方式"。

向数组尾部添加元素:push、下标赋值与concat之争

var arr = [1,2,3,4,5]; var arr2 = []; arr.push(6); arr[arr.length] = 6; arr2 = arr.concat([6]);

三种写法结果相同(数组都变成[1,2,3,4,5,6]),但语义有本质区别:

  • arr.push(6):原地修改原数组,返回新长度;
  • arr[arr.length] = 6:由于length恒等于"当前最大下标 + 1",arr[arr.length]恰好指向末尾的下一个空位,效果等同于push,同样原地修改原数组;
  • arr.concat([6]):不修改原数组,而是返回一个包含原数组全部元素与新增元素的新数组,因此必须用arr2接住返回值。

原文档特别提醒:前两种方法都会修改原始数组。如果调用方依赖原数组不被改动,concat才是唯一安全的写法——这也是"性能优先"之外必须考虑的功能约束。

移动端实测(ops/sec,越大越快)

原文档在 2015-2016 年代的移动端浏览器上测得以下数据:

浏览器arr.push(6)arr[arr.length] = 6arr2 = arr.concat([6])
Android (v4.2.2)3 319 6943 319 694(与前等同)比其他两法慢 50.61%
Chrome Mobile (v33.0.0)慢 66.74%6 125 975(基准)慢 87.63%
Safari Mobile (v9)慢 40.19%7 452 898(基准)慢 49.78%

移动端平均结论:arr[arr.length] = 6平均 5 632 856 ops/sec,arr.push(6)慢 35.64%,arr.concat([6])慢 62.67%。

桌面端实测

浏览器arr.push(6)arr[arr.length] = 6arr2 = arr.concat([6])
Chrome (v48.0.2564)慢 61.94%21 602 722(基准)慢 87.45%
Firefox (v44)56 032 805(基准)慢 0.52%慢 87.36%
IE (v11)慢 39.61%67 197 046(基准)慢 93.41%
Opera (v35.0.2066.68)慢 71.60%30 775 071(基准)慢 83.70%
Safari (v9.0.3)42 670 978(基准)慢 0.80%慢 76.07%

桌面端平均结论:arr[arr.length] = 6平均 42 345 449 ops/sec,arr.push(6)慢 34.66%,arr.concat([6])慢 85.79%。

从这份数据可以读出两个稳定规律:在绝大多数被测浏览器中,arr[arr.length] = item与push性能相当甚至更快,而concat由于需要分配新数组并复制全部元素,几乎总是最慢的(慢 49%~93%)。差异最大的场景出现在 Firefox 与 Safari 上,push与下标赋值几乎打平(差距不足 1%)。原文档当时通过 jsPerf 基准测试工具测得上述结果,并提醒读者:不同引擎的优化策略不同,结论会随环境变化,建议在自己的目标环境里复测。

向数组头部添加元素:unshift与[0].concat(arr)的对决

var arr = [1,2,3,4,5]; arr.unshift(0); [0].concat(arr);

原文档特别点出两者差异:unshift会编辑原始数组(原地修改,返回新长度);而[0].concat(arr)返回一个新数组,原数组保持不变。若要保留原数组用于后续逻辑,必须使用后者并接收返回值。

移动端实测

浏览器arr.unshift(0)[0].concat(arr)
Android (v4.2.2)慢 97.85%1 808 717(基准)
Chrome Mobile (v33.0.0)慢 99.86%1 269 498(基准)
Safari Mobile (v9)3 250 184(基准)慢 33.67%

移动端平均结论:[0].concat(arr)平均 4 972 622 ops/sec,arr.unshift(0)慢 64.70%。

桌面端实测

浏览器arr.unshift(0)[0].concat(arr)
Chrome (v48.0.2564)慢 96.77%2 656 685(基准)
Firefox (v44)慢 99.72%8 039 759(基准)
IE (v11)慢 98.31%3 604 226(基准)
Opera (v35.0.2066.68)慢 97.44%4 102 128(基准)
Safari (v9.0.3)12 356 477(基准)慢 15.17%

桌面端平均结论:[0].concat(arr)平均 6 032 573 ops/sec,arr.unshift(0)慢 78.65%。

头部插入的结果比尾部更悬殊:除了 Safari 外,[0].concat(arr)几乎全面碾压unshift,差距普遍高达 96%~99%。其原理并不难理解——unshift需要在数组头部腾出空位,必须把原有元素全部后移一位(相当于一次整体拷贝加移位);而concat在部分引擎中可以利用内存复制等底层优化直接拼接出新数组,避免了逐元素搬移的开销。当插入频率高、数组规模大时,头部插入优先选择[item].concat(arr)(并接受"返回新数组"的副作用),是在这些被测引擎上的普遍更优策略。

向数组中间添加元素:splice依然是最优解

var items = ['one', 'two', 'three', 'four']; items.splice(items.length / 2, 0, 'hello');

Array.prototype.splice(start, deleteCount, ...items)的三个关键参数决定了它的行为:

  • start:开始修改的位置(索引)。负数表示从数组末尾倒数;大于数组长度时按数组长度处理;
  • deleteCount:从start起要删除的元素个数。传0表示只插入、不删除——这正是"向中间插入元素"的标准用法;
  • ...items:要插入的元素,可以是多个,以逗号分隔依次插入。

上例中items.length / 2 === 2,因此元素'hello'被插入到索引 2 处('two'与'three'之间),结果为['one', 'two', 'hello', 'three', 'four']。splice是原地修改方法,会直接改写原数组。

原文档明确给出结论:在中间插入场景下,splice是最简单也是最高效的方式,作者在多款浏览器与操作系统上重复测试,结果均保持一致。相比用"拆成前后两段再重组"的写法,splice由引擎原生实现,避免了额外的数组分配与拼接开销。

实测数据的正确打开方式

需要特别说明的是,上文所有 ops/sec 数据均来自原文档在 2015-2016 年特定浏览器版本(Android v4.2.2、Chrome Mobile v33、Safari Mobile v9、Chrome v48、Firefox v44、IE v11、Opera v35、Safari v9.0.3)下通过 jsPerf 得到的测试结果。它们用于说明"不同写法之间存在数量级差异"这一结论非常直观,但不代表当今引擎的绝对性能——V8、SpiderMonkey 等引擎对push、unshift的优化策略早已多次迭代。正如原文档结尾所建议的:这些 tip 的价值在于启发你关注写法的性能差异,并鼓励你在自己的目标浏览器与操作系统上自行测试,用数据而不是直觉做决策。仓库中另一篇 tip Tip to measure performance of a JavaScript block 就专门介绍了用高精度时间戳测量 JS 代码块性能的方法,可作为自建基准的起点。

从 jstips 仓库延伸:数组操作的完整工具箱

本 tip 是 jstips 仓库(README.md)中编号为00的第一篇技巧。围绕"数组增删改查"这一主题,仓库里还收录了多篇可以串联阅读的姊妹篇:

  • Two ways to empty an array:list = []只是把变量指向新数组、旧引用仍持有数据;list.length = 0才会真正清空原数组内容并同步影响所有引用——与本篇"原地修改 vs. 新数组"的语义一脉相承;
  • Shuffle an Array:基于 Fisher-Yates 算法的原地洗牌,用到了本文arr[i] = arr[j]式的下标直写;
  • 3 Array Hacks:稀疏数组的map陷阱、用展开运算符传空参、[...new Set(arr)]去重;
  • Looping over arrays:while、经典for、forEach、map、reduce、filter等遍历方案的完整梳理;
  • Array average and median:基于reduce与排序求均值、中位数。

这些 tip 共同勾勒出数组操作的最佳实践图谱。如果你希望为仓库贡献新技巧,可以参考 CONTRIBUTING.md 的提交规范与 POST_TEMPLATE.md 的写作模板;本 tip 还提供了 西班牙语版 与 简体中文版 翻译,方便对照阅读。

小结:三种场景的最优选择

插入位置首选写法备选写法注意事项
尾部arr[arr.length] = itemarr.push(item)两者均原地修改;arr.concat([item])返回新数组但最慢
头部[item].concat(arr)arr.unshift(item)在多数被测引擎中concat明显更快,但返回新数组
中间arr.splice(index, 0, item)—deleteCount = 0表示只插入;原地修改原数组

最终的选择公式其实很简单:优先用下标赋值或push写尾部、用concat拼接写头部、用splice写中间;当副作用(是否修改原数组)比性能更重要时,则一律改用concat系列写法。至于极端场景下的最优解,记得回到自己的目标环境里跑一遍基准,让数据说话。

  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询