在Web前端开发中,如何优化前端数组处理方法?也许你已经遇到过讨论,我们是否应该使用Array
方法(filter
,map
,reduce
等)或循环(for
,for...of
,等)遍历集合。
关于数组的一些方法,我们分享过文章:
大家有兴趣的,可以去一一了解下。一段好的代码,会让你的程序性能更好且易理解,运行速度加快很多,相信大家都会知道这一点。
性能和可读性是选择一个而不是另一个的两个常见论点。让我们更好地看一下循环和Array
方法之间的差异并进行比较,以便我们自己决定。
性能
在ECMAScript 5之前,我们所拥有的只是循环和jQuery $.each
。哪种迭代最快的方法经常引起争议。从那时起,情况有了很大的改善。从那时起,我们智能手机中的CPU的性能就超过了计算机,我们现在可以在客户端做更多很棒的工作,而不会崩溃整个浏览器。尽管如此,Array
仍然提高了方法的性能。
TIPs: 如果要遍历大量项目,则可能要避免使用
Array
方法。
在大多数情况下,它不应有太大的区别。对于大多数项目,很少要遍历一百多个项目的列表。您通常可以使用您认为更易读的内容。只要确保始终检查低端设备的性能即可。
可读性
我们可以同意可读性很重要。这也是主观的。例如,让我们使用带有隐式return((a, b) => a + b
)的箭头函数。初学者可能会将其与逻辑表达式混淆。大于或等于运算符(>=
)看起来确实相似。也许他们可能没有意识到函数返回值。毕竟,返回是隐式的。训练有素的眼睛会立即注意到箭头的功能。对于所有其他语法也可以这样说。由我们决定培训初学者开发人员或不使用功能,以便任何技能水平的开发人员都可以访问代码库。话虽如此,让我们比较一些循环和Array
方法的可读性。
Array.prototype.forEach而且for...of都易于阅读。
posts.forEach((post) => console.log(post)); // or for (const post of posts) { console.log(post); }
等效的循环Array.prototype.filter
有点显式。
const drafts = posts.filter((post) => post.isDraft); // or const drafts = []; for (const post of posts) { if (post.isDraft) { drafts.push(post); } }
也是一样Array.prototype.map
。
const publishDates = post.map((post) => post.date); // or const publishDates = new Array(posts.length); for (let i = 0; i < posts.length; i++) { publishDates[i] = posts[i].date; }
Array.prototype.reduce
确实很棒,但是可能要花几秒钟来了解正在发生的事情。即使对于类似求和的运算,您可能也需要一秒钟才能理解。当reduce回调包含更多逻辑时,可读性迅速变差。
const totalWordCount = posts .reduce((result, post) => result + post.wordCount, 0); // or let totalWordCount = 0; for (const post of posts) { totalWordCount += post.wordCount; }
对于其他方法,例如Array.prototype.every
,,Array.prototype.find
和Array.prototype.some
,循环并不可怕,并且在具有早期返回功能的函数中效果更好。它们绝对比Array
方法更明确。
const isEverythingPublished = posts.every((post) => post.isDraft); // or let isEverythingPublished = true; for (const post of posts) { if (post.isDraft) { isEverythingPublished = false; break; } } // or in a function with early return function getIsEverythingPublished(posts) { for (const post of posts) { if (post.isDraft) { return false; } } return true; } const isEverythingPublished = getIsEverythingPublished(posts);
如果您喜欢明确的话,循环可能会吸引您。对我来说,它们有点冗长,我个人更喜欢Array
除之外的方法Array.prototype.reduce
。但同样,可读性是主观的。这是私事。
特点与用途
尽管看起来循环和Array
方法是做同一件事的两种方式,但是它们在显着方式上是不同的。
首先,Array
方法是同步的。如果将异步函数传递给Array
方法,它将不会等待异步函数完成。循环不依赖于回调,因此,如果您已经处于异步上下文中,则可以await
在循环块中或使用for await...of
循环。
考虑下面的示例。这是一劳永逸的操作,传递给异步函数的Array.prototype.forEach
调用被调用,但forEach
不等待该函数完成。
const urls = [ 'https://www.jiangweishan.com', 'https://www.web176.com', ]; urls.forEach(async () => { const response = await fetch(url); if (!response.ok) { console.warn(`Unable to fetch ${url}`); } });
在下面的示例中,我们将异步函数传递给Array.prototype.map
调用。因为一个异步函数总是返回Promise
时,返回值map
调用的列表Promise
。我们可传递给Promise.all
,Promise.allSettled
或Promise.race
。这样,我们可以并行运行各种异步任务并等待结果。
async function assertPageIsOk(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`"GET ${url}" responded with ${response.statusCode}`); } return true; } const urls = [ 'https://timseverien.com', 'https://timseverien.com/posts', ]; try { await Promise.all(urls.map(assertPageIsOk)); } catch (error) { console.error(error); }
让我们使前面的示例稍微复杂一点。让我们依次调用每个URL,而不是同时触发所有请求(可能导致拒绝服务)。如果其中之一返回非2xx状态代码,我们可以停止序列。
function assertPagesAreOk(urls) { return urls.reduce(async (chain, url) => { await chain; return assertPageIsOk(url); }, Promise.resolve()); } assertPagesAreOk(urls) .then(() => ...) .catch(() => ...);
这是上面的循环等效项:
async function assertPagesAreOk(urls) { for (const url of urls) { await assertPageIsOk(url); } return true; } assertPagesAreOk(urls) .then(() => ...) .catch(() => ...);
在以上所有示例中,我发现循环均等或更具可读性。
循环和Array
方法之间的第二个重要区别是数组不能无限大。通常,我们认为无限循环是一件坏事,但它们可能非常有用。
想象一下,我们正在开发一个现代散点图库,该库中我们想在画布上绘制大量点。由于数组大小有限,因此我们可以通过允许用户传递生成器函数来利用迭代器。
import createScatterPlot from 'scatman'; createScatterPlot({ data: *function() { while (true) { yield { x: Math.random(), y: Math.random(), }; } }, });
生成器函数返回生成器对象,它是迭代器的超集。它们可以转换为数组(通过[...iterator]
或Array.from(iterator)
),也可以循环使用。如前所述,数组的最大大小是一个限制。另一方面,在循环中,当循环需要下一个值时,会将各个值从迭代器中拉出。因为我们在示例中生成了无限点,所以将其转换为数组将冻结浏览器。
for (const { x, y } of data) { draw(x, y); await waitForNextFrame(); }
总结
我们已经知道循环速度更快,但是Array
当方法内部处理某些逻辑(例如创建新数组或滤除特定值)时,方法可以提高可读性。我们还看到,在处理异步任务时,循环更具可读性,并了解到循环对于迭代未知数量的项目很有用,这在迭代器中很常见。
在大多数情况下,我们将使用它们中的任何一个来遍历少于一千个项目的数组,在这种情况下,我们应该强烈倾向于更具可读性的选项。我觉得Array
除以外的方法更具可读性Array.prototype.reduce
。我经常看到前者在使用这种方法时遇到了麻烦,而没有完全了解它的行为。
无论如何,没有人比您更了解您,您的团队或您的项目。也许您是在一个全栈的团队中工作的,他们对循环比对Array
方法更熟悉。也许这些Array
方法更符合您的函数式编程原理。您(和您的团队)将不得不弄清楚这一点。
网友评论文明上网理性发言 已有0人参与
发表评论: