JavaScript reducer - 一个简单而又强大的数组方法
了解这个基本的JavaScript数组函数的内涵和外延
JavaScript reducer是最有用的数组方法之一,应该放在开发者的武库中。它是在ES5中引入的,与用于数组的for...each和map方法有些类似,但在特定情况下,它的性能和简单性都有所提高。
reduce方法对存储在数组中的每个元素执行我们提供的回调函数,并输出该操作产生的最终值。这是一种更简洁的迭代和处理存储在数组中的数据的方式。
目前,它被所有主要的浏览器版本所支持,并且在Node.js中从10.0版本开始可用。
今天,我们将探讨这种减少方法。我们将通过在不同场景下使用Javascript还原器的方法和时间指南。
那我们就开始吧!
Javascript reduce方法的参数
reduce方法接受两个参数:一个是作为回调的数组的reducer函数,另一个是可选的initialValue 参数。reducer函数需要四个参数。accumulator,currentValue,currentIndex, 和array:
arr.reduce(callback( accumulator, currentValue, [, index[, array]] )[, initialValue])
一个Javascript数组还原方法的实例:
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => accumulator + currentValue);
这个reduce方法与下面的for...each循环做了同样的工作,但代码行数较少:
const array = [0, 1, 2, 3, 4];
let total = 0
array.forEach(num => {
total += num
});
reduce方法是如何使用这些参数实现的呢?
由reduction函数返回的值被分配到accumulator 变量。在每次通过数组项目的迭代中,累积器的值被更新为返回的结果。在迭代结束时,累加器的最终值作为reduce 函数的输出被返回。
如果传递了一个initialValue 参数,在第一次执行还原器函数时,accumulator 将等于initialValue ,currentValue 将等于存储在数组中的第一个元素。如果没有传递初始值,accumulator 将等于数组的第一个值,currentValue 将等于第二个值。
让我们看看在下面的例子中,每次回调函数被调用时,这些参数的值是如何变化的。在这里,我们没有传递一个initialValue 参数:
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => accumulator + currentValue);
| 累积器 | currentValue | currentIndex | 数组 | 返回值 | |
|---|---|---|---|---|---|
| 1 | 0 | 1 | 0 | [0, 1, 2, 3, 4] | 1 |
| 2 | 1 | 2 | 1 | [0, 1, 2, 3, 4] | 3 |
| 3 | 3 | 3 | 2 | [0, 1, 2, 3, 4] | 6 |
| 4 | 6 | 4 | 3 | [0, 1, 2, 3, 4] | 10 |
这个函数的最终输出是10 。
接下来,让我们看看当传递一个initialValue ,它是如何工作的:
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => accumulator + currentValue, 12);
| 累加器 | currentValue | currentIndex | 数组 | 返回值 | |
|---|---|---|---|---|---|
| 1 | 12 | 0 | 0 | [0, 1, 2, 3, 4] | 12 |
| 2 | 12 | 1 | 1 | [0, 1, 2, 3, 4] | 13 |
| 3 | 13 | 2 | 2 | [0, 1, 2, 3, 4] | 15 |
| 4 | 15 | 3 | 3 | [0, 1, 2, 3, 4] | 18 |
| 5 | 18 | 4 | 4 | [0, 1, 2, 3, 4] | 22 |
这个函数输出的值是22 。
什么时候使用Javascript reducer
Reduce方法提供了一种独特的方式来迭代数组中的项目并处理它们。那么,在什么情况下我们可以从这种唯一性中获益呢?
计算一个数组中的数值之和
这与我们在前面的例子中的做法类似。唯一的区别是我们必须为initialValue 参数传递0:
let total = [34, 12, 143, 13, 76].reduce((accumulator, currentValue) => accumulator + currentValue, 0);
扁平化一个数组
如果我们有一个数组,我们可以使用reduce方法将其扁平化,创建一个没有嵌套数组的单一数组:
let array = [[0, 1], [2, 3], [4, 5], [5, 6]];
let flattenedArray = array.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
我们传递一个空数组作为初始值,这样第一个数组中的项目就会和它连接起来,创建一个扁平化的数组。
如果第一个数组有超过一层的嵌套数组,我们可以递归地调用reduce函数进行扁平化,然后将它们与最终的数组连接起来:
let nestedArray = [[1, [2, 3]], [4, 5], [[6, 7], [8, 9]]];
function flattenArray(nestedArray) {
return nestedArray.reduce(
(accumulator, currentValue) =>
accumulator.concat(
Array.isArray(currentValue) ? flattenArray(currentValue) : currentValue
),
[]);
}
let flattenedArray = flattenArray(nestedArray);
如果回调接受的当前值是一个数组,通过isArray方法验证,我们就递归地对其调用flattenArray函数。如果当前值不是一个数组,我们就简单地将该值与最终的扁平化数组连接起来。
通过一个属性对一个数组的对象进行分组
假设我们有一个国家对象的数组。我们想根据大洲来分组数组中的每个国家。我们可以使用reduce方法来完成这个任务:
let countries = [
{name: "Germany", continent: "Europe"},
{name: "Brazil", continent: "South America"},
{name: "India", continent: "Asia"},
{name: "France", continent: "Europe"},
{name: "South Korea", continent: "Asia"},
]
let groupedCountries = countries.reduce(
(groupedCountries, country) => {
if (!groupedCountries[country.continent]){
groupedCountries[country.continent] = []
}
groupedCountries[country.continent].push(country)
return groupedCountries
},
{});
在回调函数中,我们为不在groupedCountries地图中的每个大陆创建一个新的键,并分配一个空数组作为其值。然后,我们将每个国家对象推送到由其各自大陆存储的数组中。
使用reduce()来代替filter().map()。
在Javascript中,我们使用过滤器方法来过滤存储在数组中的项目,使用回调。我们使用map方法,使用回调里面传递的逻辑,用旧数组创建一个新数组。有时,我们必须使用这两个方法,一个接一个地创建一个新的数组,其中包含我们使用某些条件过滤的结果。
与其使用两个数组方法,你可以使用Javascript数组减少方法来完成同样的任务。这将减少完成时间,因为现在你只需在数组中迭代一次,而不是两次。
例如,让我们来看看下面这个场景,我们想创建一个大于30的数字的平方根数组:
let numbers = [3, 21, 34, 121, 553, 12, 53, 5, 42, 11];
let newArray = numbers.filter(number => number > 30).map(number => Math.sqrt(number));
使用reduce实现的相同场景看起来是这样的:
let numbers = [3, 21, 34, 121, 553, 12, 53, 5, 42, 11];
let newArray = numbers.reduce((accumulator, current) => {
if (current > 30) {
accumulator.push(Math.sqrt(current))
}
return accumulator
}, []);
在回调中,我们简单地检查数字是否大于30,并将其平方根添加到累积器数组中。你必须传递一个空数组作为初始值才能得到这个结果。
建立你自己的还原器
在这一节中,我们将自己实现减速器函数,看看事情是如何在引擎盖下工作的。这将使你更好地了解在你的程序中何时使用Javascript reducer以获得最佳性能:
Array.prototype.reduceConceptual = function(callback, initial) {
if (!this) {
throw Error('Array.prototype.forEach called on null or undefined');
}
if (!callback || typeof callback !== 'function') {
throw Error('callback is not a function');
}
let acc = initial;
for(i=0;i<this.length;i++) {
acc = callback(acc, this[i], i, this);
}
return acc;
}
首先,我们检查reduce方法是否是在一个空对象或未定义对象上调用的。然后,我们检查传递的回调是否是一个函数。
在初始类型检查之后,我们将传递的初始值分配给累积器。然后我们在数组中循环,为数组中的每一项调用回调。在执行结束时,我们必须返回累加器的值。
我们使用这个实现只是为了帮助你理解reduce方法的实际工作原理。例如,你可以看到它使用了一个for循环来遍历数组的外壳。
然而,请注意,你不应该在你的生产代码中使用这个实现。事实上,将方法原型化为Javascript标准类型是一种糟糕的编码实践,你不应该沉迷其中。
总结
在这篇文章中,我们讨论了最有用的Javascript数组方法之一,Javascript reducer。我们讨论了如何以及何时使用它,甚至从头开始建立我们自己的reduce方法。
我希望这些知识能够帮助你在将来发现可以通过使用reducer来解决的问题。其中一些用例与forEach,map, 和filter 数组方法重叠。所以你应该知道挑选那些可以用reduce方法优化解决的情况。