写在开头
文章有点长,但不粗,忍一忍~
- 引言中的案例基本可以应对常见的所有场景
- 第二小节开始会详细介绍 reduce 的语法
1. 引言
-
reduce是 JavaScript 数组的一种高阶函数,主要用于将数组中的所有元素通过指定的函数合并为单一值。它可以用于求和、拼接、统计等多种场景。// 列举一些常见的场景 /* 数组求和:将数组中的所有数字进行相加 */ const numbers = [1, 2, 3, 4]; const total = numbers.reduce((count, current) => count + current, 0); /* 计算数组中的最大值或最小值:找到数组中的最大值或最小值 */ const numbers = [1, 2, 3, 4]; const max = numbers.reduce((result, current) => Math.max(acc, current), numbers[0]) /* 稍加讲解 - reduce 本身用于将数组中的所有元素通过一个累加器(result)合并成一个单一的值 - “初始值 numbers[0]”。reduce方法的第二个参数是初始值,在这里设置的为 numbers 数组的第一个元素。这意味着第一次调用回调函数时,acc 的值是 numbers[0],而 current 的值是数组的 第二个元素(numbers[1]) */ /* 统计元素频率:统计数组中每个元素出现的次数。 */ const strings = ['apple', 'banana', 'apple', 'origin', 'banana']; const totals = strings.reduce((total, string) => { total[string] = (total[string] || 0) + 1; return total }, {}) /* 数组扁平化:将嵌套的数组扁平化为单一数组 */ const nestedArr = [[1, 2], [3, 4], [5]] const flatArr = nestedArr.reduce((flat, current) => flat.concat(current), []) /* 数组转对象:将数组转换为对象,例如将标准键值对数组转为对象 */ const mapsObj = [['a': 1], ['b', 2]]; const reduceObj = mapsObj.reduce((count, [key, value]) => ({...count, [key]: value}), {}) /* 合并对象:合并多个对象为一个对象 */ const objects = [{a: 1}, {b: 2}, {c: 3}]; const merged = objects.reduce((count, obj) => ({...count, ...obj}), {}) /* 生成累计和数组:生成一个新的数组,其中每个元素是源数组的累计和。 */ const numbers = [1, 2, 3, 4, 5]; const addSum = numbers.reduce((count, current, index) => { if(index === 0) return [current]; count.push(count[index - 1] + current); return count; }, []) /* 稍加讲解 - if(index === 0) return [current] 如果是第一个元素,就直接返回,也可理解为累加的初始条件 - count[index - 1] + current 正常累加每一项的值,进行推数组 - return count 返回每次更新后的 count 数组,以便下一次迭代继续使用 */ -
reduce方法的灵活性和强大功能使其成为处理数组数据时非常实用的工具,能够提高代码的简洁性和可读性。
2. reduce 的基本概念
- 定义和语法:
array.reduce(callback(prev, curr, index, arr)[, initialValue]),其中callback是执行计算的函数,initialValue是可选的初始值。 - 参数解释:
- (prev)累加器:上一次调用
callback返回的累积值,或初始值。 - (curr)当前值:数组中正在处理的元素。
- (index)当前索引:当前值在数组中的索引。
- (arr)源数组:调用
reduce的数组。
- (prev)累加器:上一次调用
3. reduce 的基本用法
-
示例:简单求和
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, current) => acc + current, 0); // acc => 0(初始值) => 1(第一次 0 + 1) => 3(第二次 1 + 2) => 6(第三次 3 + 3) => 10(第四次 6 + 4) console.log(sum); // 输出:10 -
示例:计算数组中的最大值
const numbers = [1, 2, 3, 4]; const max = numbers.reduce((result, current) => Math.max(result, current), numbers[0]) console.log(max); // 输出:4 /* 初始值 numbers[0] - reduce 方法的第二个参数是初始值。在这里,初始值设置为 numbers 数组的 第一个元素。 - 第一次调用回调函数的时候,result 的值是 numbers[0], 而 current 的值是数组中的第二个元 素 Numbers[1] 运行过程 - reduce 会遍历数组numbers的每一个元素 - 第一次: result = numbers[0], current = numbers[1] - 第二次: result 更新为 第一次返回的值,current = numbers[2] - 依次类推,直到遍历完成整个数组 示例 初始值: result = 1 第一次比较 Math.max(1, 2) => 2 第二次比较 Math.max(2, 3) => 3 第三次比较 Math.max(3, 4) => 4 最终, max 的值为 4 */
4. reduce 的进阶应用
-
示例:将数组转化为对象
const arr = ['a', 'b', 'c']; const obj = arr.reduce((acc, current) => { acc[current] = current.toUpperCase(); return acc; }, {}); /* 代码的功能是小写的key,大写的值 初始值: {} 第一次: {a: A} 第二次: {a: A, b: B} 第三次: {a: A, b: B, c: C} */ console.log(obj); // 输出:{ a: 'A', b: 'B', c: 'C' } -
示例:扁平化嵌套数组
const nestedArr = [[1, 2], [3, 4], [5]]; const flatArr = nestedArr.reduce((acc, current) => acc.concat(current), []); /* 代码的功能将数组拍平一层,根据需求可能还要拍平多层 初始值: [] 第一次: [1, 2] 第二次: [1, 2, 3, 4] 第三次: [1, 2, 3, 4, 5] */ console.log(flatArr); // 输出:[1, 2, 3, 4, 5] -
示例:统计数组中元素的出现次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'banana']; const count = fruits.reduce((acc, fruit) => { acc[fruit] = (acc[fruit] || 0) + 1; return acc; }, {}); /* 初始值: {} 第一次: {apple: 1} 第二次: {apple: 1, banana: 1} 第二次: {apple: 2, banana: 1} 第二次: {apple: 2, banana: 1, orange: 1} 第二次: {apple: 2, banana: 2, orange: 1} 第二次: {apple: 2, banana: 3, orange: 1} */ console.log(count); // 输出:{ apple: 2, banana: 3, orange: 1 }
5. 解决常见难点
-
初始值的设置:解释初始值的作用和影响。
-
reduce 方法的初始值决定了累加器的初始状态,并且在没有提供初始值的情况下,第一次调用回调函数时,累加器将会使用数组中的第一个元素。
-
初始值的设置会影响结果,尤其在空数组或某些特定情况下。
/* 提供初始值 */ const numbers = [1, 2, 3, 4, 5]; const countNumbers = numbers.reduce((result, current) => result + current, 0); console.log(countNumbers); // 输出:15 // 这个例子是初始值为0,累加器从0开始。 /* 不提供初始值 */ const numbers = [1, 2, 3, 4, 5]; const countNumbers = numbers.reduce((result, current) => result + current) console.log(countNumbers) // 输出15 // 不提供初始值,累加器从数组的第一个元素1开始,第一次调用是,result是1,current是2重点:当没有初始值的时候,累加器会从第一个元素开始!
但是,也会有特例
// 当为空数组的时候,提供初始值会返回初始值本身,不提供初始值则会报错! const emptyArray = []; const sumCount = emptyArray.reduce((result, current) => result + current, 0); console.log(sumCount); // 输出:0 // 没有初始值会抛出错误 try { const sumCount = emptyArray.reduce((result, current) => result + current); } catch (error) { console.log(error.message); // 输出:Reduce of empty array with no initial value <直译:reduce 得到了空数组没有初始值> }
-
-
有初始值和无初始值的区别
-
初始值的来源
- 有初始值
- 累加器从提供的初始值开始,无论数组的内容如何。
- 第一次调用回调函数时,累加器的值为初始值,当前元素为数组的第一个元素。
- 无初始值
- 累加器的初始值为数组的第一个元素,第一次调用回调函数的时,累加器的值是数组的第一个元素,当前元素是第二个元素。
- 如果数组为空,调用
reduce将会报错
- 有初始值
-
处理空数组
-
有初始值
-
如果数组为空,reduce将直接返回初始值
const result = [].reduce((acc, current) => acc + current, 0); console.log(result); // 输出:0
-
-
无初始值
-
如果数组为空,reduce 将会抛出错误,因为没有元素可供使用
try { const result = [].reduce((acc, current) => acc + current); } catch (error) { console.log(error.message); // 输出:Reduce of empty array with no initial value }
-
-
-
结果的计算
-
有初始值
-
结果可能与初始值相关,例如在求和时,可以将初始值设置为10,则最终结果会是数组和10的总和
const numbers = [1, 2, 3]; const countNumbers = numbers.reduce((result, current) => result + current, 10); console.log(countNumbers); // 输出:16 (10 + 1 + 2 + 3)
-
-
无初始值
-
结果完全依赖于数组的内容。如果数组只有一个元素,结果就是这个元素;如果数组为空,则会抛出错误。
const numbers = [1, 2, 3]; const countNumbers = numbers.reduce((result, current) => result + current); console.log(countNumbers); // 输出:6 (1 + 2 + 3)
-
-
-
总结
有初始值: 更加灵活,能安全处理空数组,并且可以影响最终结果。
无初始值:简单且清晰,但在某些情况下(如空数组)会导致报错。
这里呢,小编建议总是加上初始值,因为这样对排错、代码更加友好,思路更清晰
-
-
reduce与forEach的对比-
目的和使用场景
-
reduce
-
旨在从数组中生成一个单一的值(如数字、对象、数组等)
-
通常用于聚合、变换或计算的操作 -
例子:计算总和、扁平化数组、统计频率等
-
-
forEach
-
仅用于遍历数组中的每个元素,执行副作用操作(如打印、修改外部变量等) -
不返回值,不能直接用于生成结果。
-
-
-
返回值
-
reduce
-
返回最终的累加值
-
每次调用返回的值作为下次迭代的累加器
-
-
forEach
-
返回
Undefined,无法直接用作数组生成。const numbers = [1, 2, 3]; // 需要外部变量辅佐新的数组生成 let sum = 0; numbers.forEach(num => { sum += num; }) console.log(sum) // 输出: 6
-
-
-
链式调用
-
reduce
-
可以与其他数组方法链式调用,形成更复杂的操作。
const numbers = [1, 2, 3]; const result = numbers.map(num => num * 2) .reduce((pre, curr) => pre + curr, 0); console.log(result)
-
-
forEach
- 由于不返回值,无法进行链式调用。
-
-
总结
- reduce: 用于从数组生成单一值,支持复杂数据处理和链式调用。
- forEach:用于遍历数组并执行操作,不返回结果,适合简单的副作用。
-
-
典型错误及其解决方案
-
未考虑初始值
-
错误:在处理空数组时未提供初始值,导致运行时错误。
const result = [].reduce((pre, curr) => pre + curr) // 报错 -
解决方案:
总是为 reduce 提供一个初始值,确保即使数组为空时,也能得到期望结果。const result = [].reduce((pre, curr) => pre + curr, 0) // 输出: 0
-
-
累加器未返回
-
错误:在回调函数中未返回累加器,导致最终结果不正确。
const numbers = [1, 2, 3]; const sum = numbers.reduce((pre, curr) => { pre += curr // 忘记返回 }, 0) console.log(sum) // 输出: undefined -
解决方案: 确保每次迭代都显式返回累加器
const numbers = [1, 2, 3]; const sum = numbers.reduce((pre, curr) => { return pre += curr // 返回累加器 }, 0) console.log(sum) // 输出: 6
-
-
错误的数据类型
-
错误:累加器与当前元素类型不匹配,导致结果错误。
const number = [1, '2', 3]; const sum = number.reduce((pre, curr) => pre + curr, 0) // 结果为字符串 console.log(sum) // 输出: '123' -
解决方案:确保在处理前对数据进行适当的类型转换
const number = [1, '2', 3]; const sum = number.reduce((pre, curr) => pre + Number(curr), 0) console.log(sum) // 输出: 6
-
-
6. 实际应用场景
-
示例项目:使用
reduce进行统计商品销售数据const salesData = [ { productId: 'A', amount: 100 }, { productId: 'B', amount: 150 }, { productId: 'A', amount: 200 }, { productId: 'C', amount: 300 }, { productId: 'B', amount: 100 }, { productId: 'C', amount: 250 }, ]; // 使用 reduce 进行汇总 const totalSales = salesData.reduce((acc, current) => { // 检查当前商品 ID 是否已在累加器中 if (!acc[current.productId]) { acc[current.productId] = 0; // 初始化总销售额 } acc[current.productId] += current.amount; // 累加销售额 return acc; // 返回累加器 }, {}); console.log(totalSales); // 输出:{ A: 300, B: 250, C: 550 } -
上诉代码中,可以统计的数据通常会很多,这里只是一种情况,多数据的情况下可以自行拓展
- 例如: acc[current.productId + 'Count'] += 1
- 例如: acc[current.productAmount + 'Count'] += current.amount