精简的力量:优化 JavaScript 代码以提高速度和效率
大家好!这是我的第一篇帖子!
本文将探讨JavaScript 数组的reduce方法,我觉得这个方法有时会被人忽略。我会先简要介绍一下 reduce 方法,然后将其与其他迭代方法进行比较。
TLDR
- 检查你的数组迭代过程。
- 请使用;
mapfilterreduce - 散布(例如
[...list])大型列表是个坏主意;推送至list instead reduce比map和更快filter,并且for in适用于大型列表
个人笔记
我是一名前端开发人员,工作经验不到两年,这是我的第一篇文章。
我这么说是想让你们知道我还是个新手,所以欢迎大家提出任何反馈意见,无论是关于技术内容、写作还是个性(请保持建设性)。
动机:通往单循环的路径
在审查一些人的代码(有时也包括我自己的代码😬)时,我意识到很容易对数组进行不必要的迭代,而这有时reduce可以改进代码。
例如,如果我有一个人员列表,并且想要获取所有成年人的姓名列表,很容易想到:“首先我需要遍历filter列表,获取所有成年人(在我所在的国家/地区年龄大于 18 岁),然后我需要遍历map列表并返回他们的姓名”。
我相信箭头函数使这种思考更加容易,因为它消除了编写循环语句的需要function。
然而,为什么不遍历人员列表,并将成年人的名字添加到空列表中呢?简而言之,就是这个reduce意思。虽然有时人们也会忽略这种方法,但
使用循环也能轻松实现这一点。for
减少方法介绍
接下来我将简要介绍一下该reduce方法的工作原理,如果您已经熟悉该方法,请直接跳到基准测试部分。
理论
在 JavaScript 中,我们有多种方法可以遍历数组,例如map`if`、for`if` 或 `if` reduce。最后一种方法或许是三种方法中最不“友好”和常用的,但它也十分重要,我们稍后会看到。
该方法接收两个参数:
- 一个回调函数,用于返回每次迭代中累积的值;
- 一个初始值,将用于开始累积。
回调函数本身最多可以接收四个参数:
- (
accumulator有时称为total或缩写为acc)是每次迭代中“添加”的值; - 即
currentValue,正在迭代的数组的当前元素 - (可选)数组中
currentIndex元素的位置。常用;currentValue - 可选参数,用于指定要迭代的数组。由于通常迭代的是之前声明的变量,因此很少使用此选项。不过,它仍然很有用,因为如果链式调用方法(例如,调用 `sorted(
initialArray.sort(...).reduce(...))`),则数组参数将是排序后的数组,而不是初始数组。
你可能会注意到我使用了与累积相关的术语。这是因为 reduce 函数的作用就是累积:它从一个值开始,每次迭代都会将一个值“加”到该值上(我加了引号,因为不一定涉及代数加法)。
我们来看一段代码:
实际案例
我只想给你一些使用 reduce 的基本例子,因为我的目标不是让你成为reduce专家(我也不声称自己是),而是让你记住这个方法。
获取所有成年人的姓名。
让我们回顾一下前面的例子。如果对 `A`map和 `B` 也执行相同的操作filter,则可以这样做:
peopleList.filter((person) => person.age >= 18)
.map((adult) => adult.name);
使用reduce,你会做类似这样的事情:
peopleList.reduce((acc, person) => {
if (person.age >= 18) acc.push(person.name);
return acc;
}, []);
这样一来,你只需要用到一个循环而不是两个。这样是不是就不那么“友好”了?你来告诉我。
还有很多其他用途reduce和使用方法。我选择使用 `<div>`,arrow function但如果你不想用也可以。在这个CodePen中,你可以找到:
map对和filter、for in和之间检索成年人姓名的比较reduce;-
另一个例子是计算列表中所有数字的总和。这是使用 . 时引入的一个典型示例
reduce。我之所以不在这里详细解释,是因为我不想让“它
reduce只能用于计算”这种观念在你脑海中根深蒂固。没错,它确实可以用于计算,但并非仅限于此,正如一位老师曾经告诉我的那样。在这个例子中,我还详细介绍了如何将其
reduce与常规函数和内联返回一起使用;
基准测试迭代方法
了解我的人都知道,我有点痴迷于避免重复劳动,提高速度和表现。
这就是我文章的由来。
map我想测试一下和filter、 和之间在性能上是否存在差异reduce。然后我决定也把 加入for in到测试中。
终身学习!
所以我做了一个初步的基准测试,很快就发现了一个问题:
⚠传播会减慢reduce方法速度!
你看,在reducer 函数中,我是acc这样返回的:
return condition ? [...acc, item] : acc;
对于短列表来说,这并不是什么大问题,但是对于非常大的列表来说,这种语法使得该reduce方法速度慢了很多(您将会看到)。
当我第一次开发基准测试并发现reduce(我在文章中提倡的方法)速度比之前慢得多时,我感到非常惊讶map and filter!然而,经过一番研究,我发现这是因为每次迭代都会创建一个对象,并将成千上万个元素分布到该对象上,因此需要改变策略:
if(condition) acc.push(item);
return acc;
现在,每当我在我的项目中使用它时,我不会传播它,reduce而是将项目推送到。acc
基准
因此,为了开发我的基准测试,我创建了另一个CodePen 示例,其中:
- 生成一个包含 100,000 个随机数的列表;
- 每种方法运行某个任务 100 次,每次执行都进行计时;
- 每种方法都取平均值;无论迭代次数和方法如何,任务本身都是获得小于 16 的二进制转换数字。这不是一个“现实世界”的例子,但足以达到这个目的。
你会看到我添加了使用 spread和push 的reduce方法,这样你就可以看到性能上的差异。那么,让我们看看哪种方法最快。
结果:获胜者是……
减少!当然了reduce。如果不是这样,我就不会写这篇文章了。😄
你会注意到我没有在图表中添加reduce使用spread 方法的时间,因为它长达8.8秒(比 push 方法慢 3000 多倍)!所有其他方法的测量单位均为毫秒。
如您所见,三种迭代方法之间的差异并不大,但这并不能改变 `a`map和 ` filterb` 比 `c` 慢 3 毫秒的事实reduce。当然,对于较小的列表,这种差异将微乎其微。
关于这一基准,需要考虑以下几点:
- 这段代码看起来可能过于复杂,但这是我找到的避免在每次迭代或方法中重新创建函数的最佳方法。
- 我把列表长度增加到了一个可能不太合理的程度。很少有项目会处理包含成千上万个元素的列表。但如果你确实需要这样做,而且网站性能是一个重要问题,那么减少列表长度就是你的好朋友;
- 纯 JavaScript 通常不会处理大型列表。处理大型列表有多种方法,例如虚拟化或分页,因此请注意。
- 我展示的运行时间是在CodePen上运行代码获得的,因此在生产环境中,在服务器上执行速度可能会更快。
就这些啦!
感谢阅读我的第一篇文章,希望对你有所帮助(否则,我不会退还你花费的时间)。
