利用 Reduce 提升性能的 1 个简单技巧
你有没有注意到,先使用map再使用filter,或者反过来,这种情况非常普遍?你知道如果只使用reduce而不是 map,就可以将计算时间缩短一半吗?
我们将首先回顾三种数组方法。如果您已经熟悉这些方法,可以直接跳到第 5 部分。
内容
概述
Map、filter 和 reduce 都是 Array 原型中的方法。它们用途各不相同,但都涉及使用回调函数遍历数组中的元素。
地图
Map 函数返回一个与调用它的原始数组长度相同的数组。它接受一个参数,即一个函数,该函数可以接受 3 个参数:
- 要迭代的当前项
- 当前项的索引
- 原始数组
Map 操作不会改变原始数组,而是创建一个新数组,因此必须像这样将 Map 的值赋给一个变量:
const nums = [10, 20, 30, 40, 50];
// assign value of map into result variable
const result = nums.map(function(item, index, arr) {}); // RIGHT
nums.map(function(item, index, arr) {}); // WRONG!
我们来看一个例子:我们有一个年份数组,想要获取这些年份所代表的年龄,并且还要保留年份的原始值。这意味着我们的整数数组将被映射到一个对象数组,每个对象都有两个属性:年份和年龄。
例子:
const years = [1991, 1999, 2000, 2010, 2014];
const currentYear = (new Date).getFullYear();
const ages = years.map(function(year) {
const age = currentYear - year;
// each element will now be an object with 2 values: year & age
return {
year,
age
}
});
我们现在有一个名为ages 的数组,其结构如下:
ages = [
{year: 1991, age: 29},
{year: 1999, age: 21},
{year: 2000, age: 20},
{year: 2010, age: 10},
{year: 2014, age: 6}
]
筛选
顾名思义,` filter` 函数
会将我们想要的元素从数组中筛选到一个新数组中,并忽略任何我们不需要的元素。 它接受一个参数,即一个函数,该函数可以接受三个参数:
- 要迭代的当前项
- 当前项的索引
- 原始数组
该函数充当谓词,将数组的原始内容返回到新赋值的变量中。与 map 不同,filter 不一定返回与调用它的数组长度相同的数组。
与 map 类似,filter 不会改变原始数组,因此 filter 的值必须赋给一个变量。
让我们来看一个例子,假设我们有一个years数组,表示人们的出生年份,我们想要一个新的数组,其中只包含那些相当于一个人超过 18 岁的年份。
例子:
const years = [1991, 1999, 2000, 2010, 2014];
const currentYear = (new Date).getFullYear();
const over18 = years.filter(function(year) {
// if the year equates to over 18, then put that year
// into our new over18 array
return (currentYear - year) > 18;
});
我们现在有一个名为over18 的数组,其结构如下:
over18 = [1991, 1999, 2000];
减少
reduce 方法 会将数组简化为单个值。该单个值可以是任何 JavaScript 类型,例如字符串、数字,甚至是数组或对象。
它需要两个参数:
-
一个接受 4 个参数的函数:
a. 累加器
b. 要迭代的当前项
c. 当前项目索引
d. 源数组
-
我们想要返回的单个值的初始值
与 map 和 filter 一样,reduce 不会改变原始数组,因此 reduce 的值必须赋给一个变量。
例子:
const nums = [10, 20, 30, 40, 50];
const sum = nums.reduce(function(total, num) {
total += num;
return total;
}, 0);
console.log(sum); // 150
也可以这样写:
const nums = [10, 20, 30, 40, 50];
const reducer = (total, num) => total += num;
const sum = nums.reduce(reducer, 0);
我们初始化数组 ` nums`和reducer函数,该函数会将一个数字加到当前总值上。然后我们初始化`sum`并调用 `reduce` 方法,将reducer作为第一个参数传递,并将总值的初始值(在本例中为0)作为参数传递。由于 0 是初始值,因此它将是第一次迭代中总值的值。
地图 + 过滤器 = 减少
现在我们已经回顾了 map、filter 和 reduce 的功能以及它们之间的区别,接下来让我们来理解本文的标题。
在编程中,我们经常需要对数组中的元素进行筛选,并对其内容进行一些细微的修改。这里我特意用了“修改”这个词,因为我们知道这些方法并不会真正改变原始数组。
记住:
- 过滤器会保留数组中我们感兴趣的元素,并将其赋值给一个新变量。
- map 函数总是将一个数组赋值给一个新变量,该变量的长度与调用它时所用的数组的长度相同。
既然使用 reduce 就能在一半的时间内完成任务,为什么还要调用 filter 和 map 呢?
使用 reduce 函数,我们可以一步完成过滤和映射数组内容的任务,而不是两步。
让我们来看一个例子,我们有一个years数组,表示人们的出生年份,我们只想保留 18 岁以上的人,并计算出这些人的年龄。
例子:
const years = [1991, 1999, 2000, 2010, 2014];
const currentYear = (new Date).getFullYear();
const reducer = (accumulator, year) => {
const age = currentYear - year;
if (age < 18) {
return accumulator;
}
accumulator.push({
year,
age
});
return accumulator;
}
const over18Ages = years.reduce(reducer, []);
现在,我们基本上已经将过滤器部分和映射部分中的示例合并到了一个归约操作中。结果如下:
over18Ages = [
{year: 1991, age: 29},
{year: 1999, age: 21},
{year: 2000, age: 20}
]
我们最初的数组years有 5 个元素。如果我们使用 map 后接 filter,则需要 10 次迭代才能得到与 reduce 5 次迭代相同的结果。然而,map、filter 和 reduce 的底层实现略有不同,那么它们在性能上真的会有区别吗?
表现
让我们来看一个极端、不切实际但又简单的例子……
let arr = [];
// populate array with 100,000,000 integers
for (let i = 0; i < 100000000; i++) {
arr.push(i);
}
// calculate time taken to perform a simple map,
// of multiplying each element by 2
const mapStart = performance.now();
const mapResult = arr.map((num) => num * 2);
const mapEnd = performance.now();
// calculate time taken to perform a simple filter,
// of only returning numbers greater than 10,000
const filterStart = performance.now();
const filterResult = mapResult.filter((num) => num > 10000);
const filterEnd = performance.now();
// calculate time taken to perform a simple reduce,
// of populating an array of numbers whose initial value
// is greater than 10,000, then doubling this number
// and pushing it to our total
const reduceStart = performance.now();
const reduceResult = arr.reduce((total, num) => {
const double = num * 2;
if (double <= 10000) {
return total;
}
total.push(double);
return total;
}, []);
const reduceEnd = performance.now();
console.log(`map time (ms): ${mapEnd - mapStart}`);
console.log(`filter time(ms): ${filterEnd - filterStart}`);
console.log(`reduce time(ms): ${reduceEnd - reduceStart}`);
// map time (ms): 2415.8499999903142
// filter time(ms): 3142.439999995986
// reduce time(ms): 3068.4299999993527
我把 1 亿个整数塞进一个数组里确实有点极端,但我只是想向你们展示一下性能上的差异(以秒为单位)。结果显示,使用 reduce 函数计算出的结果,耗时 3.14 秒,而使用 filter 和 map 函数则需要 5.56 秒才能完成。请注意,这仅仅是处理一个整数数组。如果处理的是字符串或对象,计算时间会更长。
结论
当你发现自己在使用map后紧接着使用filter,或者反过来时,不妨考虑改用reduce,这样计算时间就能缩短一半!作为程序员,你需要权衡利弊,因为性能上的提升可能会以代码可读性的降低为代价。
Reduce有很多应用场景,这只是其中之一。
编程愉快😊
题图来自Unsplash用户chuttersnap
文章来源:https://dev.to/jrdev_/1-simple-trick-to-boost-performance-using-reduce-5c2b
