array.prototype.concat 在高频合并大量小数组时会引发严重内存压力和gc开销,因其每次调用都新建数组且无法复用中间内存;推荐改用 flatmap、预先收集后单次 concat 或手动 push 摊平。

Array.prototype.concat 在合并大量小数组时,看似轻量,实则容易引发不可忽视的临时内存压力——关键不在“大”,而在“多”和“频”。它每次调用都会创建一个全新数组,并一次性预分配足够容纳所有输入元素的空间。当面对成百上千个小数组(比如每个长度 2–10)连续 concat 时,引擎无法复用中间内存,反而会高频次地申请、填充、丢弃短生命周期数组,导致垃圾回收(GC)频繁触发,拖慢整体性能。
为什么“小数组多”比“单个大数组”更危险
单次 concat 大数组虽消耗内存,但行为可预期;而大量小数组 concat 的问题在于累积效应和碎片化:
- 每次调用都新建数组:哪怕只拼两个长度为 3 的数组,concat 也要分配一个长度为 6 的新数组,写入 6 个值,然后旧数组(若无引用)等待 GC —— 这个过程重复上千次,就是上千次小内存块分配与释放
- 引擎难以优化连续小分配:V8 等引擎对批量大内存有优化(如预估容量、内存池复用),但对高频小数组 concat 往往按“普通对象分配”处理,易产生堆内存碎片
-
链式调用放大开销:如
a.concat(b).concat(c).concat(d),会生成 a+b、(a+b)+c、((a+b)+c)+d 三个中间数组,而实际只需要最终结果 —— 中间数组完全多余
典型高风险场景识别
以下模式容易踩坑,尤其在构建工具、日志聚合、分片数据组装等逻辑中常见:
- 循环中反复调用
result = result.concat(chunk)(即累加式拼接) - 从 Map/Set 或多个 Promise.all 结果中逐个提取数组再 concat
- 模板渲染层将数十个组件的 classList 数组(如
['btn', 'primary'])逐一 concat 成最终 className 字符串数组
更轻量的替代方案
目标不是“不用 concat”,而是避免它在高频、小粒度场景下的滥用。推荐按需选用:
-
用 Array.from + flatMap 替代链式 concat:如把
[a, b, c].map(x => x).flat()改为Array.from({ length: totalLen }, (_, i) => /* 按索引查对应数组 */),或直接[a, b, c].flatMap(x => x)—— flatMap 内部不建中间数组,且现代引擎对其做了专门优化 -
预先收集再单次 concat:把所有待拼小数组先 push 到一个数组里,最后调用一次
allChunks.concat(...),把 N 次分配压成 1 次 - 用 for 循环 + push 手动摊平:对性能敏感路径,显式遍历各小数组并 push 到目标数组,内存增长线性可控,无额外中间对象
本质上,concat 是为“明确、有限、结构清晰”的合并设计的;把它当作流式拼接积木,就容易低估背后的分配成本。观察你的调用频次和输入规模,比单纯看单次数组大小更能判断风险。










