前端性能优化--JavaScript 数组解构
更新日期:
之前在给大家介绍性能相关内容的时候,经常说要给大家讲一些更具体的案例,而不是大的解决方案。
这不,最近刚查到一个数组的性能问题,来给大家分享一下~
数组解构的性能问题
ES6 的出现,让前端开发小伙伴们着实高效工作了一番,我们常常会使用解构的方式拼接数组,比如:
1 | // 浅拷贝新数组 |
这样的代码经常会出现,毕竟对于大多数场景来说,很少会因为这样简单的数组结构导致性能问题。
但实际上,如果在数据量大的场景下使用,数组解构不仅有性能问题,还可能导致 JavaScript 爆栈等问题。
两者差异
使用concat
和...
拓展符的最大区别是:...
使用对象需为可迭代对象,当使用...
解构数组时,它会尝试迭代数组的每个元素,并将它们展开到一个新数组中。
1 | a = [1, 2, 3, 4]; |
如果解构对象不可迭代,则会报错:
1 | a = [1, 2, 3, 4]; |
除此之外,concat()
用于在数组末尾添加元素,而...
用于在数组的任何位置添加元素:
1 | a = [1, 2, 3, 4]; |
性能差异
由于concat()
方法的使用对象为数组,基于次可以进行很多优化,而...
拓展符在使用时还需要进行检测和迭代,性能上会是concat()
更好。
1 | let big = new Array(1e5).fill(99); |
上述代码在我的 Chrome 浏览器上输出结果为:
1 | concat-big: 35.491943359375 ms |
也有网友提供的测试数据为:
浏览器 | [...a, ...b] |
a.concat(b) |
---|---|---|
Chrome 113 | 350 毫秒 | 30 毫秒 |
Firefox 113 | 400 毫秒 | 63 毫秒 |
Safari 16.4 | 92 毫秒 | 71 毫秒 |
以及不同数据量的对比数据:
更多数据可参考How slow is the Spread operator in JavaScript?:
Array.push()
爆栈
当数组数据量很大时,使用Array.push(...array)
的组合还可能出现 JavaScript 堆栈溢出的问题,比如这段代码:
1 | const someArray = new Array(600000).fill(1); |
这是因为解构会使用apply
方法来调用函数,即Array.prototype.push.apply(newArray, someArray)
,而参数数量过大时则可能超出堆栈大小,可以这样使用来解决这个问题:
1 | newArray = [...someArray]; |
内存占用
之前在项目中遇到的特殊场景,两份代码的差异只有数组的创建方式不一致:
使用newArray = [].concat(oldArray)
的时候,内存占用并没有涨,因此不会触发浏览器的 GC:
但使用newArray = [...oldArray]
解构数组的时候,内存占用会持续增长,因此也会带来频繁的 GC,导致函数执行耗时直线上涨:
可惜的是,对于这个困惑的程度只达到了把该问题修复,但依然无法能建立有效的 demo 复现该问题(因为项目代码过于复杂无法简单提取出可复现 demo)。
个人认为或许跟前面提到的 JavaScript 堆栈问题有些关系,但目前还没有更多的时间去往底层继续研究,只能在这里小小地记录一下。
参考
结束语
今天给大家介绍了一个比较具体的性能问题,可惜没有更完整深入地往下捞到 v8 的实现和内存回收相关的内容,以后有机会有时间的话,可以再翻出来看看叭~
希望有一天能有机会和能力解答今天的疑惑~
码生艰难,写文不易,给我家猪囤点猫粮了喵~
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢