请不要过度使用数组方法
合并你的数学运算
使用复合布尔表达式
利用操作数省略
结论
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
在现代 JavaScript 出现之前,遍历数组的唯一方法是使用经典的 C 风格for循环。这至少可以说是一种繁琐的操作。它过于冗长,而且会产生大量样板代码。随着函数式编程概念的兴起,我们今天喜爱并享受的数组方法应运而生。得益于 `map` forEach、 `map` map、filter`map` 和`map` 等方法reduce,遍历数组从未如此简单。再加上ES6 的箭头函数,它们更是简洁无比。
除了简洁之外,这些数组方法(本质上就是功能更强大的for循环)还允许我们根据需要链式调用各种数组操作,而不会牺牲代码的可读性(具体取决于你的代码)。欣赏一连串优美的顺序方法调用真是一种享受。每次方法调用都清晰地展现数组的逐步操作,这使得代码读起来更加自然流畅。过去需要好几行代码才能完成的工作,现在只需一行代码就能搞定。
尽管数组方法几乎完全消除了循环的需求for,但它们也带来了新的问题。尽管方法调用链看起来很优雅,但我们必须记住,链中每个方法的调用都意味着数组的一次全新迭代。为了编写高性能代码,我们必须牢记,这些长链意味着更多的迭代。
合并你的数学运算
为了说明不必要的长链问题,考虑一个从-21 到 2的数字数组2。我们的目标是求出这些数字平方的三倍之和。乍一看,我们似乎可以用一系列的 `get()`map和 ` reduceget()` 调用来解决这个问题。
const nums = [ -2, -1, 0, 1, 2 ];
const sum = nums
.map(x => x * x)
.map(x => x * 3)
.reduce((prev, curr) => prev + curr, 0);
这确实能达到我们的目标。唯一的问题是它有三个链式方法。三个链式方法意味着三次全新的数组迭代。我们可以通过console.log在返回每个回调函数之前 添加一个中间函数来证明这一点。
但我不会在这篇文章里这么做,因为你现在可能已经明白我的意思了。如果这听起来效率很低,尤其是在规模化处理时,那么你的判断是正确的。为了提高这段代码的性能,我们只需找到一种方法来组合方法调用,从而最大限度地减少 CPU 对同一数据数组的迭代次数。
const nums = [ -2, -1, 0, 1, 2 ];
// Level 1: Combine the `map` calls
const level1Sum = nums
.map(x => 3 * x ** 2)
.reduce((prev, curr) => prev + curr, 0);
// Level 2: Combine _everything_
const level2Sum = nums
.reduce((prev, curr) => prev + 3 * curr ** 2, 0);
使用复合布尔表达式
同样的规则也适用于布尔表达式和filter方法。假设我们有一个User对象数组。我们想要找到User当前拥有高级帐户的对象。然后,从这些帐户中,我们查找年龄超过指定限制的管理员18。
class User {
constructor(isAdmin, hasPremium, age) {
this.isAdmin = isAdmin;
this.hasPremium = hasPremium;
this.age = age;
}
}
// Array of `User` accounts
const users = [
new User(false, false, 9),
new User(false, true, 30),
new User(true, true, 15),
new User(true, true, 19),
new User(false, true, 3)
];
我们可以使用复合布尔表达式来组合每个条件,而不是直接组合数学运算。这样可以最大限度地减少数组迭代次数。
// Level 0: Chain _everything_
const level0 = users
.filter(user => user.isAdmin)
.filter(user => user.hasPremium)
.filter(user => user.age > 18);
// Level 2: Combine _everything_
const level3 = users
.filter(user => (
user.isAdmin
&& user.hasPremium
&& user.age > 18
));
利用操作数省略
值得注意的是,类似的代码仍有进一步优化的空间。通过巧妙地安排布尔条件,代码运行速度可以略微提升。这是因为ECMAScript 规范规定,逻辑 AND 运算符 ( &&) 在遇到值为 0 的表达式时,必须立即停止对后续操作数的求值false。
function willRun() {
console.log('I just stopped the `&&` operator from evaluating the next operand.');
return false;
}
function neverRuns() { console.log('This function will never run.'); }
// 'I just stopped the `&&` operator from evaluating the next operand.'
true && willRun() && neverRuns();
为了编写(稍微)性能更高的代码,必须将更有可能被求值为真值的布尔表达式false放在复合布尔条件的开头,以防止不必要地执行和求值后续操作数。
// Arranging conditions properly will
// make your code run slightly faster.
arr.filter(x => (
x.mostLikelyToBeFalse
&& x.moreLikelyToBeFalse
&& x.likelyToBeFalse
&& x.leastLikelyToBeFalse
));
结论
当然,我举的例子都很简单。运行这些例子不会带来显著的性能差异,甚至可能根本没有差异。不必要的过长迭代链对性能的影响只有在规模较大、计算量更大的情况下才会显现出来。大多数情况下,我们无需担心这个问题。此外,大多数迭代链的长度甚至不会超过四次。
本文旨在提醒大家,虽然我们可以链式调用方法,但这并不意味着我们应该过度使用这种方法。作为开发者,我们有责任确保不滥用这种能力。无论影响多么微小,链式调用中每个方法都会对性能产生影响。如果你能从本文中记住一点,那就是:链越长,迭代次数就越多。
除非你想承受不必要的迭代带来的后果,否则请不要“过度链接”数组方法。
文章来源:https://dev.to/somedood/please-dont-overchain-array-methods-49fa