先说结论
在 reduce
循环体中使用 concat
方法或 ...
拓展符会导致严重的性能问题
测试数据
const data = Array(100000).fill(null).map((_, idx) => ({ num: Math.random(), idx }))
性能对比
测试数据由同一设备(M1 Macbook Air)使用
console.time
方法得出,同一功能不同实现性能差异最高可达数万倍
reduce
+ push
// 2.96ms
result = data.reduce((rst, cur) => {
if (cur.num > 0.5) rst.push(cur.idx)
return rst
}, [])
filter
+ map
// 4.46ms
result = data.filter(i => i.num > 0.5).map(i => i.idx)
reduce
+ concat
// 4746.03ms
result = data.reduce((rst, cur) => cur.num > 0.5 ? rst.concat(cur.idx) : rst, [])
reduce
+ ...
// 24900.75ms
result = data.reduce((rst, cur) => cur.num > 0.5 ? [...rst, cur.idx] : rst, [])
原理分析
通过 Performance 面板可以看到,虽然内存占用没有增长到极大值,但是出现了超高频的 GC,那么导致问题的原因也就很清晰了,每次循环中调用 concat
等 API 会不断地创建新的对象,高频的对象创建导致高频的 GC 触发,而在 GC 执行过程中,浏览器还将无法处理其他事情,比如响应用户的操作,渲染动画,从而造成页面的卡顿