reduce
是 JavaScript 数组的高阶函数之一,用于对数组中的元素进行累积操作,最终返回一个累积结果。reduce
接受一个回调函数作为参数,该回调函数在每次迭代中执行,并接受四个参数:累积值(也称为累加器),当前元素值,当前索引和原始数组。
语法:
array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
其中:
callback
:表示回调函数,它执行数组元素的累积逻辑。accumulator
:表示累积值,它是在每次回调函数执行时累积计算的结果。如果提供了initialValue
,那么累积值将从initialValue
开始;否则,它将从数组的第一个元素开始。currentValue
:表示当前元素的值。currentIndex
:表示当前元素的索引(可选)。array
:表示原始数组(可选)。initialValue
:表示累积值的初始值(可选)。
常见解决问题的场景例子:
- 数组求和:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // Output: 15
- 数组求平均值:
const numbers = [10, 20, 30, 40, 50];
const average = numbers.reduce((accumulator, currentValue, currentIndex, array) => {
accumulator += currentValue;
if (currentIndex === array.length - 1) {
return accumulator / array.length;
} else {
return accumulator;
}
}, 0);
console.log(average); // Output: 30
- 数组中的最大值和最小值:
const numbers = [8, 3, 11, 6, 21, 4];
const max = numbers.reduce((accumulator, currentValue) => Math.max(accumulator, currentValue), numbers[0]);
const min = numbers.reduce((accumulator, currentValue) => Math.min(accumulator, currentValue), numbers[0]);
console.log(max); // Output: 21
console.log(min); // Output: 3
- 数组元素计数:
const fruits = ['apple', 'banana', 'orange', 'apple', 'banana', 'apple'];
const count = fruits.reduce((accumulator, currentValue) => {
accumulator[currentValue] = (accumulator[currentValue] || 0) + 1;
return accumulator;
}, {});
console.log(count); // Output: { apple: 3, banana: 2, orange: 1 }
- 字符串中字符出现的次数:
const str = "hello world";
const charCount = str.split('').reduce((accumulator, currentValue) => {
accumulator[currentValue] = (accumulator[currentValue] || 0) + 1;
return accumulator;
}, {});
console.log(charCount); // Output: { h: 1, e: 1, l: 3, o: 2, ' ': 1, w: 1, r: 1, d: 1 }
- 对象属性求和:
假设有一个包含商品信息的数组,每个商品对象都有 price
属性。现在我们想要计算所有商品价格的总和:
const products = [
{ name: 'Product A', price: 100 },
{ name: 'Product B', price: 200 },
{ name: 'Product C', price: 150 },
];
const total = products.reduce((accumulator, product) => accumulator + product.price, 0);
console.log(total); // Output: 450
- 数组扁平化:
假设有一个多维数组,我们想要将它扁平化为一个一维数组:
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = nestedArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
console.log(flattenedArray); // Output: [1, 2, 3, 4, 5, 6]
- 数据分类:
假设有一个包含人员信息的数组,每个人员对象都有 age
属性,现在我们想要根据人员年龄将其分类:
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 25 },
{ name: 'Dave', age: 35 },
];
const peopleByAge = people.reduce((accumulator, person) => {
const age = person.age;
if (!accumulator[age]) {
accumulator[age] = [];
}
accumulator[age].push(person);
return accumulator;
}, {});
console.log(peopleByAge);
/* Output:
{
25: [
{ name: 'Alice', age: 25 },
{ name: 'Charlie', age: 25 }
],
30: [
{ name: 'Bob', age: 30 }
],
35: [
{ name: 'Dave', age: 35 }
]
}
*/
- 数组去重:
通过 reduce
将数组去重:
const numbers = [1, 2, 3, 2, 4, 1, 5, 3, 6];
const uniqueNumbers = numbers.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5, 6]
- 函数管道(Function Piping):
const data = [1, 2, 3, 4, 5];
const addOne = num => num + 1;
const double = num => num * 2;
const subtractFive = num => num - 5;
const result = data.reduce((accumulator, currentValue) => {
return [addOne, double, subtractFive].reduce((acc, fn) => fn(acc), currentValue);
}, 0);
console.log(result); // Output: 3 (先加一,再乘以2,再减去5)
- 多条件数据分类:
假设有一个包含人员信息的数组,每个人员对象有 age
和 gender
属性。现在我们想要根据人员的年龄和性别进行多条件分类:
const people = [
{ name: 'Alice', age: 25, gender: 'female' },
{ name: 'Bob', age: 30, gender: 'male' },
{ name: 'Charlie', age: 25, gender: 'male' },
{ name: 'Dave', age: 35, gender: 'male' },
{ name: 'Eve', age: 25, gender: 'female' },
];
const peopleByAgeAndGender = people.reduce((accumulator, person) => {
const { age, gender } = person;
if (!accumulator[age]) {
accumulator[age] = {};
}
if (!accumulator[age][gender]) {
accumulator[age][gender] = [];
}
accumulator[age][gender].push(person);
return accumulator;
}, {});
console.log(peopleByAgeAndGender);
/* Output:
{
25: {
female: [
{ name: 'Alice', age: 25, gender: 'female' },
{ name: 'Eve', age: 25, gender: 'female' }
],
male: [
{ name: 'Charlie', age: 25, gender: 'male' }
]
},
30: {
male: [
{ name: 'Bob', age: 30, gender: 'male' }
]
},
35: {
male: [
{ name: 'Dave', age: 35, gender: 'male' }
]
}
}
*/
- 数组分块:
假设有一个排序好的数组,我们想要将其分块成指定大小的子数组:
const sortedArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const chunkSize = 3;
const chunkedArray = sortedArray.reduce((accumulator, currentValue, currentIndex) => {
const chunkIndex = Math.floor(currentIndex / chunkSize);
if (!accumulator[chunkIndex]) {
accumulator[chunkIndex] = [];
}
accumulator[chunkIndex].push(currentValue);
return accumulator;
}, []);
console.log(chunkedArray);
/* Output:
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10]
]
*/
- 数组分组:
假设有一个数组,我们想要根据某个条件对数组进行分组:
const numbers = [10, 20, 35, 45, 55, 60, 70];
const groups = numbers.reduce((accumulator, currentValue) => {
const groupKey = currentValue >= 50 ? 'greaterThan50' : 'lessThan50';
if (!accumulator[groupKey]) {
accumulator[groupKey] = [];
}
accumulator[groupKey].push(currentValue);
return accumulator;
}, {});
console.log(groups);
/* Output:
{
greaterThan50: [55, 60, 70],
lessThan50: [10, 20, 35, 45]
}
*/
当结合其他高阶用法时,reduce
可以应用于非常复杂的场景。其中一个非常复杂的场景是实现函数组合或管道。
函数组合是一种将多个函数合并成一个新函数的技术,其中每个函数的输出都是下一个函数的输入。我们可以使用 reduce
和函数组合技术来实现函数管道,将一系列函数应用于数据并依次传递结果。
函数管道示例:
假设有一个字符串数组,我们想要将其转换为大写、去除空格并拼接为一个单词:
const words = [' hello', 'world ', 'JavaScript '];
const compose = (...functions) => input => functions.reduceRight((acc, fn) => fn(acc), input);
const toUpperCase = str => str.toUpperCase();
const trim = str => str.trim();
const concatenate = (str1, str2) => str1 + str2;
const result = words.map(compose(trim, toUpperCase)).reduce(concatenate);
console.log(result); // Output: "HELLOWORLDJAVASCRIPT"
在上面的例子中,我们首先定义了三个简单的字符串处理函数:toUpperCase
(将字符串转换为大写)、trim
(去除字符串两端的空格)和 concatenate
(将两个字符串拼接在一起)。然后我们定义了一个 compose
函数,该函数接受任意数量的函数,并返回一个新函数,该新函数依次将传入的函数应用于输入数据。在 compose
函数中,我们使用 reduceRight
来依次应用函数,这样可以确保函数按照从右到左的顺序执行。
然后,我们使用 map
方法将 words
数组中的每个字符串依次传递给 compose
函数,然后使用 reduce
方法将转换后的结果拼接在一起,得到最终的结果:"HELLOWORLDJAVASCRIPT"。
这个例子展示了如何结合函数组合和 reduce
实现一个复杂的函数管道,通过这种方式,你可以在实际开发中处理更加复杂的数据转换和处理任务。
还有一种复杂的场景是使用 reduce
和递归实现树形结构的操作。这样的场景在处理层次结构数据,比如树形结构的 JSON 数据或嵌套对象数组时非常有用。
树形结构操作示例:
假设有一个包含树形结构的 JSON 数据,每个节点都有 id
和 children
属性。我们想要通过 reduce
实现根据 id
查找节点的功能:
const treeData = {
id: 1,
children: [
{
id: 2,
children: [
{
id: 3,
children: []
},
{
id: 4,
children: []
}
]
},
{
id: 5,
children: []
}
]
};
const findNodeById = (tree, id) => {
return tree.id === id
? tree
: tree.children.reduce((acc, child) => acc || findNodeById(child, id), null);
};
const foundNode = findNodeById(treeData, 3);
console.log(foundNode); // Output: { id: 3, children: [] }
在上面的例子中,我们首先定义了一个 findNodeById
函数,该函数使用 reduce
递归地遍历树形结构。它将传入的 id
与当前节点的 id
进行比较,如果匹配,就返回当前节点;否则,它会继续递归遍历节点的子节点,直到找到匹配的节点或遍历完整个树。
通过这种方式,我们可以使用 reduce
和递归实现复杂的树形结构操作,比如根据 id
查找节点、计算节点的深度或广度优先遍历等。
reduce
在处理树形结构的例子中可以非常有用。在树形结构中,每个节点可能会有子节点,形成层次结构。使用 reduce
可以在树形结构中实现递归遍历、搜索、转换等操作。下面是一个在树形结构中使用 reduce
的例子:
假设有以下树形结构数据表示文件系统的目录结构:
const fileSystem = {
name: 'root',
type: 'folder',
children: [
{
name: 'folder1',
type: 'folder',
children: [
{
name: 'file1.txt',
type: 'file',
},
{
name: 'file2.txt',
type: 'file',
},
],
},
{
name: 'folder2',
type: 'folder',
children: [
{
name: 'file3.txt',
type: 'file',
},
],
},
],
};
- 计算文件数量:
我们可以使用 reduce
在树形结构中递归计算文件的数量:
const countFiles = (node) => {
if (node.type === 'file') {
return 1;
} else if (node.type === 'folder') {
return node.children.reduce((acc, child) => acc + countFiles(child), 0);
} else {
return 0;
}
};
const totalFiles = countFiles(fileSystem);
console.log(totalFiles); // Output: 3
- 获取指定文件路径:
我们可以使用 reduce
在树形结构中递归查找指定文件路径:
const findFileByPath = (node, path) => {
if (node.name === path) {
return node;
} else if (node.type === 'folder') {
for (const child of node.children) {
const foundFile = findFileByPath(child, path);
if (foundFile) {
return foundFile;
}
}
}
return null;
};
const filePath = 'file3.txt';
const foundFile = findFileByPath(fileSystem, filePath);
console.log(foundFile); // Output: { name: 'file3.txt', type: 'file' }
- 转换树形结构:
我们可以使用 reduce
在树形结构中递归转换节点数据:
const transformTree = (node) => {
if (node.type === 'file') {
return { file: node.name };
} else if (node.type === 'folder') {
return {
folder: node.name,
children: node.children.reduce((acc, child) => [...acc, transformTree(child)], []),
};
} else {
return null;
}
};
const transformedTree = transformTree(fileSystem);
console.log(transformedTree);
/* Output:
{
folder: 'root',
children: [
{
folder: 'folder1',
children: [
{ file: 'file1.txt' },
{ file: 'file2.txt' }
]
},
{
folder: 'folder2',
children: [
{ file: 'file3.txt' }
]
}
]
}
*/