ZetCode

JavaScript reduce

最后修改于 2023 年 10 月 18 日

在本文中,我们将展示如何在 JavaScript 语言中使用规约操作。

reduce 函数

reduce 函数对数组的每个元素执行一个规约器函数,产生一个单一的输出值。规约器由程序员提供。

规约器函数接受四个参数

规约器返回的值被分配给累加器。累加器在整个数组的每次迭代中都会被记住,最终成为最终的单个结果值。

规约操作非常强大。它们允许我们计算总和、乘积、平均值、最大值和最小值,排序,反转,展平数组等等。

reduce 函数语法

reduce 函数具有以下语法

arr.reduce(callback( accumulator, currentValue[, index[, array]] )[, initialValue])

方括号中的参数是可选的。如果未提供 initialValuereduce 将从索引 1 开始执行回调函数,跳过第一个索引。如果提供了 initialValue,它将从索引 0 开始。

在我们的示例中,我们将累加器称为 total,将当前值称为 next

JS reduce - 求和与求积

在下一个示例中,我们计算值的总和和乘积。

sum_product.js
let vals = [1, 2, 3, 4, 5];

let sum = vals.reduce((total, next) => {return total + next});
let product = vals.reduce((total, next) => {return total * next});

console.log(`The sum is: ${sum}`);
console.log(`The product is: ${product}`);

总和和乘积是从一个整数数组中计算出来的。

$ node sum_product.js 
The sum is: 15
The product is: 120

JS reduce - 最小值和最大值

以下示例从数组中选取最大值和最小值。

min_max.js
let vals = [1, 2, 3, 4, 5];

const [initial] = vals;

const min = vals.reduce((total, next) => Math.min(total, next), initial);
const max = vals.reduce((total, next) => Math.max(total, next), initial);


console.log(`The minimum is: ${min}`);
console.log(`The maximum is: ${max}`);

我们使用 Math.maxMath.min 函数来获取当前总累加器值的最大值和最小值,该值在迭代过程中传递,直到返回最终值。初始值是数组中的第一个元素。请注意,在这种情况下,'total' 值是当前选择的最大值。

$ node min_max.js 
The minimum is: 1
The maximum is: 5

可以在一次操作中计算这两个值。

min_max2.js
let vals = [1, 2, 3, 4, 5];

const initials = {
    min: Number.MAX_VALUE,
    max: Number.MIN_VALUE,
};

const min_max_vals = vals.reduce(min_max, initials);
console.log(min_max_vals);


function min_max(total, next) {
    return {
        min: Math.min(total.min, next),
        max: Math.max(total.max, next),
    };
}

我们的 min_max 规约器函数返回一个带有 min 和 max 属性的对象。

$ node min_max2.js 
{ min: 1, max: 5 }

JS reduce map

在函数式 map 操作中,通过对每个元素应用一个表达式来从源数组创建一个新数组。

mapping.js
let vals = [1, 2, 3, 4, 5];

let mapped = vals.reduce((total, next) => {total.push(next * 2); return total}, []);
console.log(mapped);

该示例从初始数组创建一个新数组。每个元素乘以 2。初始值是一个空数组。规约器函数使用 push 函数将下一个值乘以 2 添加到初始数组中。

$ node mapping.js 
[ 2, 4, 6, 8, 10 ]

JS reduce filter

filter 函数式操作创建一个新数组,其中包含所有通过给定测试的元素。

filtering.js
let vals = [-1, -2, 3, 4, -5, -6];

let filtered = vals.reduce((total, next) => {
  
    if (next > 0) {
        total.push(next * 2);
    }

    return total;
}, []);

console.log(filtered);

该示例创建一个新数组,其中仅包含正值。仅当下一个值大于零时,规约器函数才会将其添加到总数组中。

$ node filtering.js 
[ 6, 8 ]

JS reduce - 展平数组

以下示例展平一个数组。

flatten.js
let vals = [[0, 1], [2, 3], [4, 5], [5, 6]];

let flattened = vals.reduce((total, next) => total.concat(next), []);

console.log(flattened);

初始值是一个空数组,规约器函数将新的嵌套数组与 concat 函数合并到该数组中。

$ node flatten.js 
[
  0, 1, 2, 3,
  4, 5, 5, 6
]

JS reduce 平均值

计算平均值时,我们还利用索引和源数组。

average.js
let vals = [1, 2, 3, 4, 5];

let average = vals.reduce((total, next, idx, array) => {

  total += next;

  if (idx === array.length - 1) { 
    return total / array.length;
  } else { 
    return total;
  }
});

console.log(average);

该示例计算值的平均值。

if (idx === array.length - 1) { 
  return total / array.length;
} else { 
  return total;
}

当我们在规约器函数中到达数组的末尾时,我们将总值(在这种情况下是值的总和)除以元素的数量。这是要返回的最终值。否则,我们将下一个值添加到总值中,并从规约器中返回总值。

$ node average.js 
3

值 1 到 5 的平均值为 3。

JS reduce reverse

在下一个示例中,我们使用扩展运算符 ...。扩展运算符返回数组的所有元素。

reversed.js
let vals = [88, 28, 0, 9, 389, 420];

let reversed = vals.reduce((total, next) => {return [next, ...total]}, []);

console.log(reversed);

在每次迭代中,规约器函数返回一个新数组,其中当前元素位于第一个位置,然后是总数组中已有的所有元素。

$ node reversing.js 
[ 420, 389, 9, 0, 28, 88 ]

JS reduce - 唯一值

以下示例创建一个新数组,其中仅包含唯一值。

unique_vals.js
let vals = [1, 1, 2, 2, 3, 4, 5, 5];

let unique_vals = vals.reduce((total, next) => {

    if (total.includes(next)) {

        return total;
    } else {

        return [...total, next];
    }

}, []);

console.log(unique_vals);

规约器函数使用 includes 函数检查该值是否已在总数组中。仅当 includes 函数返回 false 时,它才将下一个值添加到总数组中。

$ node unique_vals.js 
[ 1, 2, 3, 4, 5 ]

JS reduce 管道

我们可以在规约器函数中链式调用函数。

piping.js
function inc(val) { 
    return val + 1; 
}

function dec(val) {

    return val - 1; 
}

function double(val) { 

    return val * 2; 
}

function halve(val) { 

    return val / 2; 
}

let pipeline = [inc, halve, dec, double];

let res = pipeline.reduce((total, fn) => {
    
  return fn(total);
}, 9);

console.log(res);

在示例中,我们在规约器中链式调用了四个函数。这些函数应用于初始值。

$ node piping.js 
8

JS reduce - 柯里化和函数组合

柯里化是将具有多个参数的函数转换为具有单个参数的嵌套函数序列。柯里化帮助我们创建组合函数,这些函数稍后会接受参数。

阅读 JavaScript 柯里化教程 了解更多关于柯里化的信息。

fun_composition.js
const double = x => x * 2
const triple = x => x * 3
const quadruple = x => x * 4

const pipe = (...funs) => input => funs.reduce(
    (total, fn) => fn(total),
    input
)

const fun1 = pipe(double)
const fun2 = pipe(double, triple)
const fun3 = pipe(triple, triple)
const fun4 = pipe(double, triple, quadruple)

console.log(fun1(2))
console.log(fun2(5))
console.log(fun3(7))
console.log(fun4(9))

pipe 函数接受任意数量的参数 - 函数。组合函数稍后接受输入值,函数在该值上运行。

const pipe = (...funs) => input => funs.reduce(
    (total, fn) => fn(total),
    input
)

pipe 函数首先将函数应用于输入值。然后将计算出的中间值传递给链中的其他函数,直到返回最终值。

const fun1 = pipe(double)
const fun2 = pipe(double, triple)
const fun3 = pipe(triple, triple)
const fun4 = pipe(double, triple, quadruple)

这些是用于特定值乘法的组合函数。

$ node fun_composition.js 
4
30
63
216

JS reduce - 统计出现次数

规约器函数可用于统计数组中元素的出现次数。

tally.js
const words = ['sky', 'forest', 'wood', 'sky', 'rock', 'cloud', 
    'sky', 'forest', 'rock', 'sky'];

const tally = words.reduce((total, next) => {

  total[next] = (total[next] || 0) + 1 ;

  return total;
}, {});


console.log(tally);

在示例中,我们有一个单词数组。一些单词被多次包含。初始值是一个空对象。规约器函数要么创建一个新属性,要么增加该属性的值。

$ node tally.js 
{ sky: 4, forest: 2, wood: 1, rock: 2, cloud: 1 }

JS reduce - 按属性分组对象

以下示例按属性对数组中的对象进行分组。

grouping.js
let users = [
  { name: 'John', age: 25, occupation: 'gardener' },
  { name: 'Lenny', age: 51, occupation: 'programmer' },
  { name: 'Andrew', age: 43, occupation: 'teacher' },
  { name: 'Peter', age: 52, occupation: 'gardener' },
  { name: 'Anna', age: 43, occupation: 'teacher' },
  { name: 'Albert', age: 46, occupation: 'programmer' },
  { name: 'Adam', age: 47, occupation: 'teacher' },
  { name: 'Robert', age: 32, occupation: 'driver' }
];

let grouped = users.reduce((result, user) => {

    (result[user.occupation] || (result[user.occupation] = [])).push(user);  
    return result;
}, {});

console.log(grouped);

我们有一个用户数组。我们按他们的职业对用户进行分组。初始值是一个空对象。结果对象将职业作为属性;每个属性包含一个具有相应职业的用户列表。

let grouped = users.reduce((result, user) => {

    (result[user.occupation] || (result[user.occupation] = [])).push(user);  
    return result;
}, {});

规约器要么创建一个带有空数组的新属性并推送第一个用户,要么将一个新用户对象添加到已创建的数组中。

$ node grouping.js 
{
  gardener: [
    { name: 'John', age: 25, occupation: 'gardener' },
    { name: 'Peter', age: 52, occupation: 'gardener' }
  ],
  programmer: [
    { name: 'Lenny', age: 51, occupation: 'programmer' },
    { name: 'Albert', age: 46, occupation: 'programmer' }
  ],
  teacher: [
    { name: 'Andrew', age: 43, occupation: 'teacher' },
    { name: 'Anna', age: 43, occupation: 'teacher' },
    { name: 'Adam', age: 47, occupation: 'teacher' }
  ],
  driver: [ { name: 'Robert', age: 32, occupation: 'driver' } ]
}

JS reduce - 将数组转换为对象

以下示例将数组转换为对象。

array2object.js
let users = [
  { id: 1, name: 'John', age: 25, occupation: 'gardener' },
  { id: 2, name: 'Lenny', age: 51, occupation: 'programmer' },
  { id: 3, name: 'Andrew', age: 43, occupation: 'teacher' },
  { id: 4, name: 'Peter', age: 52, occupation: 'gardener' },
  { id: 5, name: 'Anna', age: 43, occupation: 'teacher' },
  { id: 6, name: 'Albert', age: 46, occupation: 'programmer' },
  { id: 7, name: 'Adam', age: 47, occupation: 'teacher' },
  { id: 8, ame: 'Robert', age: 32, occupation: 'driver' }
];


let obj = users.reduce((total, e) => {

    const {id, ...attrs} = e;

    return {...total, [id]: attrs, };
}, {});


console.log(obj);

我们有一个用户对象数组。使用规约器,我们将数组转换为对象;用户 ID 成为结果对象中的标识属性。

const {id, ...attrs} = e;  

从当前元素(某个用户对象)中,我们将 id 值复制到 id 常量中,并将其余属性复制到 attrs 中。

return {...total, [id]: attrs, };

然后,我们构建中间(最终)对象。首先,我们扩展到目前为止在 total 中创建的所有内部属性,并添加当前属性。

注意: 使用 [id] 语法,我们在创建 JavaScript 对象时使用 id 变量的实际值作为键/属性。
$ node array2object.js 
{
  '1': { name: 'John', age: 25, occupation: 'gardener' },
  '2': { name: 'Lenny', age: 51, occupation: 'programmer' },
  '3': { name: 'Andrew', age: 43, occupation: 'teacher' },
  '4': { name: 'Peter', age: 52, occupation: 'gardener' },
  '5': { name: 'Anna', age: 43, occupation: 'teacher' },
  '6': { name: 'Albert', age: 46, occupation: 'programmer' },
  '7': { name: 'Adam', age: 47, occupation: 'teacher' },
  '8': { ame: 'Robert', age: 32, occupation: 'driver' }
}

来源

数组 reduce - 语言参考

在本文中,我们使用了 JavaScript 中的规约。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

查看 所有 JavaScript 教程。