前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端性能优化--JavaScript 数组解构

前端性能优化--JavaScript 数组解构

原创
作者头像
被删
发布2024-08-11 14:31:10
4061
发布2024-08-11 14:31:10
举报
文章被收录于专栏:被删的前端游乐场

之前在给大家介绍性能相关内容的时候,经常说要给大家讲一些更具体的案例,而不是大的解决方案。

这不,最近刚查到一个数组的性能问题,来给大家分享一下~

数组解构的性能问题

ES6 的出现,让前端开发小伙伴们着实高效工作了一番,我们常常会使用解构的方式拼接数组,比如:

代码语言:ts
复制
// 浅拷贝新数组
const newArray = [...originArray];
// 拼接数组
const newArray = [...array1, ...array2];

这样的代码经常会出现,毕竟对于大多数场景来说,很少会因为这样简单的数组结构导致性能问题。

但实际上,如果在数据量大的场景下使用,数组解构不仅有性能问题,还可能导致 JavaScript 爆栈等问题。

两者差异

使用concat...拓展符的最大区别是:...使用对象需为可迭代对象,当使用...解构数组时,它会尝试迭代数组的每个元素,并将它们展开到一个新数组中。

代码语言:ts
复制
a = [1, 2, 3, 4]
b = 'test';

console.log(a.concat(b)) // [1, 2, 3, 4, 'test']
console.log([...a, ...b]) 
// [1, 2, 3, 4, 't', 'e', 's', 't']

如果解构对象不可迭代,则会报错:

代码语言:js
复制
a = [1, 2, 3, 4]
b = 100;

console.log(a.concat(b)) // [1, 2, 3, 4, 100]
console.log([...a, ...b]) // TypeError: b is not iterable

除此之外,concat()用于在数组末尾添加元素,而...用于在数组的任何位置添加元素:

代码语言:js
复制
a = [1, 2, 3, 4]
b = [5, 6, 7, 8]

console.log(a.concat(b)) // [1, 2, 3, 4, 5, 6, 7, 8]
console.log([...b, ...a]) // [5, 6, 7, 8, 1, 2, 3, 4]

性能差异

由于concat()方法的使用对象为数组,基于次可以进行很多优化,而...拓展符在使用时还需要进行检测和迭代,性能上会是concat()更好。

代码语言:ts
复制
let big = (new Array(1e5)).fill(99);
let i, x;

console.time('concat-big');
for(i = 0; i < 1e2; i++) x = [].concat(big)
console.timeEnd('concat-big');

console.time('spread-big');
for(i = 0; i < 1e2; i++) x = [...big]
console.timeEnd('spread-big');

let a = (new Array(1e3)).fill(99);
let b = (new Array(1e3)).fill(99);
let c = (new Array(1e3)).fill(99);
let d = (new Array(1e3)).fill(99);

console.time('concat-many');
for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d)
console.timeEnd('concat-many');

console.time('spread-many');
for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d]
console.timeEnd('spread-many');

上述代码在我的 Chrome 浏览器上输出结果为:

代码语言:log
复制
concat-big: 35.491943359375 ms
spread-big: 268.485107421875 ms
concat-many: 0.55615234375 ms
spread-many: 6.807861328125 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 堆栈溢出的问题,比如这段代码:

代码语言:ts
复制
const someArray = new Array(600000).fill(1);
const newArray = [];
let tempArray = [];

newArray.push(...someArray); // JS error
tempArray = newArray.concat(someArray); // can work

这是因为解构会使用apply方法来调用函数,即Array.prototype.push.apply(newArray, someArray),而参数数量过大时则可能超出堆栈大小,可以这样使用来解决这个问题:

代码语言:ts
复制
newArray = [...someArray];

内存占用

之前在项目中遇到的特殊场景,两份代码的差异只有数组的创建方式不一致:

使用newArray = [].concat(oldArray)的时候,内存占用并没有涨,因此不会触发浏览器的 GC:

但使用newArray = [...oldArray]解构数组的时候,内存占用会持续增长,因此也会带来频繁的 GC,导致函数执行耗时直线上涨:

可惜的是,对于这个困惑的程度只达到了把该问题修复,但依然无法能建立有效的 demo 复现该问题(因为项目代码过于复杂无法简单提取出可复现 demo)。

个人认为或许跟前面提到的 JavaScript 堆栈问题有些关系,但目前还没有更多的时间去往底层继续研究,只能在这里小小地记录一下。

参考

结束语

今天给大家介绍了一个比较具体的性能问题,可惜没有更完整深入地往下捞到 v8 的实现和内存回收相关的内容,以后有机会有时间的话,可以再翻出来看看叭~

希望有一天能有机会和能力解答今天的疑惑~

查看Github有更多内容噢: https://github.com/godbasin

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数组解构的性能问题
    • 两者差异
      • 性能差异
        • Array.push()爆栈
          • 内存占用
          • 参考
          • 结束语
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档