Javascript 零基础教程

JavaScript map、filter 和 reduce

本章节将深入探讨三个最常见且最有用的高阶函数:mapfilterreduce

理解并掌握这些函数将显著提升你在 JavaScript 中处理数组和其他数据结构的能力。

1. 什么是 mapfilterreduce

mapfilterreduce 是 JavaScript 内置的数组方法,同时也是高阶函数

这意味着它们接受另一个函数作为参数,这个参数通常被称为回调函数 (callback function)

它们会遍历数组,并对每个元素(或在 filter 的情况下,对一部分元素)应用回调函数。操作的结果是根据回调函数中定义的逻辑生成一个新的数组(或在 reduce 的情况下,生成一个单一的值)。这些方法提供了一种简洁且声明式的方式来操作数组,而无需使用传统的 for 循环。

2. map()

map() 方法通过对原始数组中的每一个元素应用提供的函数来创建一个数组。当你想要以相同的方式转换数组的每个元素时,它非常有用。原始数组保持不变。

语法

const newArray = array.map(callbackFunction);
  • array: 你想要转换的原始数组。
  • callbackFunction: 为数组每个元素调用的函数。它最多接受三个参数:
    • element: 数组中当前正在处理的元素。
    • index (可选): 当前正在处理的元素的索引。
    • array (可选): 调用 map 的原始数组。
  • newArray: 一个新数组,其中每个元素都是回调函数的结果。

2.1 示例 1:数字求平方

假设你有一个数字数组,你想创建一个包含每个数字平方的新数组。

const numbers = [1, 2, 3, 4, 5];

// 使用 map 创建一个包含每个数字平方的新数组
const squaredNumbers = numbers.map(function(number) {
  return number * number;
});

console.log(squaredNumbers); // 输出: [1, 4, 9, 16, 25]
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原始数组未改变)

在这个例子中,回调函数 function(number) { return number * number; } 被应用到 numbers 数组的每个元素上。map() 方法随后构建了一个包含结果的新数组 squaredNumbers

2.2 示例 2:配合箭头函数使用 map()

箭头函数(来自第 6 模块)为编写回调函数提供了一种更简洁的方式。

const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(number => number * number); // 使用箭头函数更简洁
console.log(squaredNumbers); // 输出: [1, 4, 9, 16, 25]

2.3 示例 3:转换数组中的对象

map() 也可以用来转换对象数组。假设你有一个产品对象数组,你想创建一个只包含产品名称的新数组。

const products = [
  { id: 1, name: '笔记本电脑', price: 1200 },
  { id: 2, name: '键盘', price: 75 },
  { id: 3, name: '鼠标', price: 25 }
];

const productNames = products.map(product => product.name);
console.log(productNames); // 输出: ['笔记本电脑', '键盘', '鼠标']

2.4 示例 4:在 Map 中使用索引

如果需要,你可以在回调函数中访问索引。例如,将索引添加到每个元素中:

const numbers = [10, 20, 30];
const indexedNumbers = numbers.map((number, index) => number + index);
console.log(indexedNumbers); // 输出: [10, 21, 32]

3. filter()

filter() 方法创建一个数组,其中包含所有通过了所提供函数测试的元素。当你想要基于特定条件从数组中选择一部分元素时,它非常有用。原始数组保持不变。

语法

const newArray = array.filter(callbackFunction);
  • array: 你想要过滤的原始数组。
  • callbackFunction: 为数组每个元素调用的函数。如果该元素应该包含在新数组中,它应该返回 true,否则返回 false。它最多接受三个参数:
    • element: 数组中当前正在处理的元素。
    • index (可选): 当前正在处理的元素的索引。
    • array (可选): 调用 filter 的原始数组。
  • newArray: 一个新数组,只包含原始数组中通过测试(回调函数返回 true)的那些元素。

3.1 示例 1:过滤偶数

假设你有一个数字数组,你想创建一个只包含偶数的新数组。

const numbers = [1, 2, 3, 4, 5, 6];

// 使用 filter 创建一个只包含偶数的新数组
const evenNumbers = numbers.filter(function(number) {
  return number % 2 === 0; // 如果数字是偶数则返回 true
});

console.log(evenNumbers); // 输出: [2, 4, 6]
console.log(numbers); // 输出: [1, 2, 3, 4, 5, 6] (原始数组未改变)

在这个例子中,回调函数 function(number) { return number % 2 === 0; } 被应用到 numbers 数组的每个元素上。如果函数返回 true(意味着数字是偶数),该元素就会被包含在新数组 evenNumbers 中。

3.2 示例 2:配合箭头函数使用 filter()

同样,箭头函数提供了更紧凑的语法。

const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(number => number % 2 === 0); // 使用箭头函数更简洁
console.log(evenNumbers); // 输出: [2, 4, 6]

3.3 示例 3:基于属性过滤对象

filter() 也可以用于对象数组。假设你有一个产品对象数组,你想过滤掉价格超过 100 美元的产品。

const products = [
  { id: 1, name: '笔记本电脑', price: 1200 },
  { id: 2, name: '键盘', price: 75 },
  { id: 3, name: '鼠标', price: 25 },
  { id: 4, name: '显示器', price: 300 }
];

const expensiveProducts = products.filter(product => product.price > 100);
console.log(expensiveProducts);
// 输出:
// [
//   { id: 1, name: '笔记本电脑', price: 1200 },
//   { id: 4, name: '显示器', price: 300 }
// ]

3.4 示例 4:基于长度过滤字符串

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const longWords = words.filter(word => word.length > 6);
console.log(longWords); // 输出: ["exuberant", "destruction", "present"]

4. reduce()

reduce() 方法对数组的每个元素执行一个归约器 (reducer) 函数(由你提供),从而产生单个输出值。当你想要将数组的所有元素组合成一个值(如总和、平均值或拼接字符串)时,它非常有用。与 mapfilter 不同,reduce 不一定返回新数组,而是返回一个累计值。原始数组保持不变。

语法

const result = array.reduce(callbackFunction, initialValue);
  • array: 你想要归约的原始数组。
  • callbackFunction: 为数组每个元素调用的函数。它最多接受四个参数:
    • accumulator (累加器): 上一次调用回调时返回的累积值,或者是 initialValue(如果提供了的话)。
    • currentValue (当前值): 数组中当前正在处理的元素。
    • currentIndex (可选): 当前正在处理的元素的索引。
    • array (可选): 调用 reduce 的原始数组。
  • initialValue (可选): 用作第一次调用 callbackFunction 的第一个参数的值。如果没有提供 initialValue,数组中的第一个元素将用作初始的 accumulator 值,并且会被跳过作为 currentValue。在空数组上调用不带 initialValuereduce() 会抛出 TypeError
  • result: 归约操作产生的单一值。

4.1 示例 1:数字求和

假设你有一个数字数组,你想计算它们的总和。

const numbers = [1, 2, 3, 4, 5];

// 使用 reduce 计算数字的总和
const sum = numbers.reduce(function(accumulator, currentValue) {
  return accumulator + currentValue;
}, 0); // 0 是累加器的初始值 (initialValue)

console.log(sum); // 输出: 15
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原始数组未改变)

在这个例子中,回调函数 function(accumulator, currentValue) { return accumulator + currentValue; } 被应用到 numbers 数组的每个元素上。accumulatorinitialValue 0 开始。在每次迭代中,currentValue 被加到 accumulator 上,结果成为下一次迭代的新 accumulator

4.2 示例 2:配合箭头函数使用 reduce()

使用箭头函数使代码更简洁。

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 使用箭头函数更简洁
console.log(sum); // 输出: 15

4.3 示例 3:计算产品总价

假设你有一个产品对象数组,你想计算所有产品的总价格。

const products = [
  { id: 1, name: '笔记本电脑', price: 1200 },
  { id: 2, name: '键盘', price: 75 },
  { id: 3, name: '鼠标', price: 25 }
];

const totalPrice = products.reduce((accumulator, product) => accumulator + product.price, 0);
console.log(totalPrice); // 输出: 1300

4.4 示例 4:拼接字符串

reduce 的用途不仅限于数值计算。例如,拼接字符串数组:

const words = ['Hello', ' ', 'World', '!'];
const sentence = words.reduce((accumulator, word) => accumulator + word, '');
console.log(sentence); // 输出: Hello World!

4.5 示例 5:扁平化数组的数组

const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flatArray = nestedArray.reduce((accumulator, subArray) => accumulator.concat(subArray), []);
console.log(flatArray); // 输出: [1, 2, 3, 4, 5, 6]