JavaScript .map() 方法怎么用?

文章导读
从经典的 for 循环到 forEach() 方法,在 JavaScript 中遍历数据集有多种方式。其中最受欢迎的选择之一是 .map() 方法。.map() 方法通过对原始数组的每个元素调用一个函数并收集返回的值来创建一个新数组。.map() 方法是非变异的,这意味着它返回一个新数组而不修改原始数组。这与变异方法不同,后者会直接更改调用它们所在的数组。
📋 目录
  1. Ⅰ 引言
  2. Ⅱ 先决条件
  3. Ⅲ 如何为数组中的每个项目调用 JavaScript 函数
  4. Ⅳ 如何将 JavaScript 字符串转换为数组
  5. Ⅴ 如何在 JavaScript 库中渲染列表
  6. Ⅵ 如何重格式化 JavaScript 数组对象
  7. Ⅶ 比较 map()、forEach() 和 filter()
  8. Ⅷ 链式调用数组方法
  9. Ⅸ 性能和可读性注意事项
  10. Ⅹ 使用 .map() 的常见错误
A A

引言

从经典的 for 循环到 forEach() 方法,在 JavaScript 中遍历数据集有多种方式。其中最受欢迎的选择之一是 .map() 方法。.map() 方法通过对原始数组的每个元素调用一个函数并收集返回的值来创建一个新数组。.map() 方法是非变异的,这意味着它返回一个新数组而不修改原始数组。这与变异方法不同,后者会直接更改调用它们所在的数组。

在处理数组和转换数据时,此方法有许多实际用途。在本教程中,您将了解 JavaScript 中 .map() 的四个值得注意的用法:为数组的每个元素调用函数、将字符串转换为数组、在 JavaScript 库中渲染列表,以及重新格式化数组对象。

使用 App Platform 从 GitHub 部署您的前端应用。让 专注于扩展您的应用。

关键要点:

  • .map() 方法遍历数组并返回一个包含回调函数产生的值的新数组。
  • .map() 是一个非变异方法,这意味着它不会修改原始数组。
  • 开发者常用 .map() 来转换数据,例如修改数字、提取对象属性或重塑数据集。
  • 在像 React 这样的 JavaScript 框架中,.map() 常用于从数据数组渲染 UI 元素列表。
  • 当您需要对数组中的每个元素应用相同的转换时,.map() 是最佳选择。
  • 像 .map()、.filter() 和 .reduce() 这样的数组方法可以链式组合,以构建清晰且富有表现力的数据转换管道。
  • 当您需要返回转换后的新数组值而不是仅执行副作用时,使用 .map() 而非 .forEach()。

先决条件

本教程不需要任何编码,但如果您有兴趣跟随示例,可以使用 Node.js REPL 或浏览器开发者工具。

如何为数组中的每个项目调用 JavaScript 函数

.map() 接受一个回调函数作为其参数之一。回调函数通常将当前正在处理元素作为其第一个参数接收,这允许您处理数组中的每个值。使用此参数,您可以转换数组中的每个项目并将结果作为新数组的一部分返回。

以下是一个示例:

const sweetArray = [2, 3, 4, 5, 35];
const sweeterArray = sweetArray.map(sweetItem => {
  return sweetItem * 2;
});

console.log(sweeterArray);

此输出将记录到控制台:

Output
[ 4, 6, 8, 10, 70 ]

您可以简化此示例,使其更简洁:

// 创建一个要使用的函数。
const makeSweeter = sweetItem => sweetItem * 2;

// 我们有一个数组。
const sweetArray = [2, 3, 4, 5, 35];

// 调用我们创建的函数;这更易读。
const sweeterArray = sweetArray.map(makeSweeter);

console.log(sweeterArray);

相同的输出将记录到控制台:

Output
[ 4, 6, 8, 10, 70 ]

使用像 sweetArray.map(makeSweeter) 这样的代码可以使您的意图更清晰。

如何将 JavaScript 字符串转换为数组

.map() 方法属于数组原型。在本节中,你将使用特殊的 .call() 方法来将字符串转换为数组。你并不是在为字符串重新定义 .map(),而是借用现有的数组方法,并以字符串作为上下文来运行它。

JavaScript 既有对象,也有诸如字符串和数字等原始值。尽管字符串是原始值,但它们表现得像数组般的对象,因为它们的字符可以通过索引访问,并且它们有一个 length 属性。

JavaScript 中的方法是附加到对象上的函数。.call() 方法允许你调用一个函数,同时显式设置 this 的值。通过使用 .call(),你可以以字符串作为上下文来运行 .map() 方法。

.call() 接受要使用的上下文作为其第一个参数,后面跟着原始函数期望的任何参数。

以下是一个示例:

const name = "Sammy";
const map = Array.prototype.map;

const newName = map.call(name, eachLetter => {
  return `${eachLetter}a`;
});

console.log(newName);

此输出将被记录到控制台:

输出
[ "Sa", "aa", "ma", "ma", "ya" ]

在这里,你在字符串上使用了 .map() 的上下文,并传递了 .map() 期望的回调函数。

这之所以有效,是因为字符串是具有索引字符的类数组值。使用 .call() 允许 .map() 方法遍历这些字符,并返回一个包含转换结果的新数组。

这类似于字符串上的 .split() 方法,不同之处在于每个单独的字符在返回数组之前可以被修改。

如何在 JavaScript 库中渲染列表

像 React 这样的 JavaScript 库通常使用 .map() 来渲染列表中的项目。在 React 应用中,.map() 经常在 JSX 表达式中使用,从数据数组生成元素列表。

以下是一个 React 组件的示例:

import React from "react";
import { createRoot } from "react-dom/client";

const names = ["whale", "squid", "turtle", "coral", "starfish"];

const NamesList = () => (
  <div>
    <ul>{names.map(name => <li key={name}>{name}</li>)}</ul>
  </div>
);

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<NamesList />);

这是一个无状态的 React 组件,它渲染一个包含列表的 div。单个列表项使用 .map() 生成,通过遍历 names 数组为每个项目创建一个 <li> 元素。

该组件使用 React 渲染到具有 id 为 root 的 DOM 元素上。

如何重格式化 JavaScript 数组对象

.map() 也可以用于遍历对象数组并转换每个对象的内容。与 .map() 的其他用法类似,该方法会返回一个包含回调函数返回值的新数组。

以下是一个示例:

const myUsers = [
  { name: 'shark', likes: 'ocean' },
  { name: 'turtle', likes: 'pond' },
  { name: 'otter', likes: 'fish biscuits' },
];

const usersByLikes = myUsers.map(item => {
  const container = {};

  container[item.name] = item.likes;
  container.age = item.name.length * 10;

  return container;
});

console.log(usersByLikes);

此输出将记录到控制台:

输出
[ { shark: "ocean", age: 50 }, { turtle: "pond", age: 60 }, { otter: "fish biscuits", age: 50 } ]

在这个示例中,.map() 遍历对象数组并返回包含重格式化对象的新数组。回调函数为每个条目创建一个新对象,并使用方括号和点表示法分配新属性。

这种方法在用户界面显示数据或将其发送到应用程序其他部分之前进行转换或重组数据时非常有用。

比较 map()、forEach() 和 filter()

JavaScript 提供了几种数组迭代方法,这些方法对于处理数据集合非常有用。虽然 .map() 通常用于转换数组值,但 .forEach() 和 .filter() 等方法则有不同的用途。

理解何时使用每种方法可以帮助您编写更清晰、更易维护的代码。

例如,.map() 会转换值并返回一个新数组:

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);

console.log(doubled);

这会产生以下输出:

输出
[ 2, 4, 6 ]

相比之下,.forEach() 不会返回新数组,通常用于副作用:

numbers.forEach(num => {
  console.log(num);
});

同时,.filter() 返回一个仅包含满足条件的元素的新数组:

const evenNumbers = numbers.filter(num => num % 2 === 0);

console.log(evenNumbers);

这会产生以下输出:

输出
[ 2 ]

以下是快速总结表格:

方法 用途 返回新数组 典型用例
.map() 转换数组中的每个元素 是 转换或修改值
.forEach() 为每个元素执行一个函数 否 执行副作用,如日志记录或更新变量
.filter() 选择匹配条件的元素 是 创建数组的子集

一般来说,当您想要转换数组中的每个元素时使用 .map(),当您想要选择某些元素时使用 .filter(),当您只是想为每个项目运行一个函数而不创建新数组时使用 .forEach()。

链式调用数组方法

诸如 .map()、.filter() 和 .reduce() 等数组方法可以链式调用在一起,在单个序列中执行多个转换。因为像 .map() 和 .filter() 这样的方法会返回新数组,你可以立即在返回值上调用下一个数组方法。

链式调用可以使你的代码更简洁、更易读,因为每一步都表达一个单一、专注的操作。它还可以减少临时变量的使用,前提是你的转换管道较短且意图清晰。

链式方法的顺序很重要,因为每个方法都会接收前一步的输出。一般规则是将选择方法如 .filter() 放在较早的位置,这样后续步骤处理的元素就更少。

同样重要的是要理解,.reduce() 通常是链式调用中的最后一个方法,因为它返回单个值(例如数字、字符串或对象),而不是新数组。

例如,你可以先过滤数组,然后转换剩余的值:

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

const result = numbers
  .filter(num => num % 2 === 0)
  .map(num => num * 2);

console.log(result);

这会产生以下输出:

输出
[ 4, 8, 12 ]

在这个例子中,.filter() 首先创建一个只包含偶数的数组。然后这个结果数组被传递给 .map(),后者将每个值乘以二。

当你想将转换后的值聚合为单个结果时,也可以链式调用到 .reduce()。例如,你可以过滤并映射值,然后求和:

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

const total = numbers
  .filter(num => num % 2 === 0)
  .map(num => num * 2)
  .reduce((sum, num) => sum + num, 0);

console.log(total);

这会产生以下输出:

输出
24

在现代 JavaScript 中,链式调用数组方法常用于以清晰、声明式的方式处理数据。与编写多个循环相比,你可以将小操作组合起来,逐步转换数据。

在编写链式代码时,尽量让你的回调函数避免副作用,并始终返回一致的值。如果链式调用变得很长或难以跟踪,你可以通过将中间结果赋值给命名良好的变量来使代码更容易调试,或者在大型数据集需要最大性能时切换到循环。

性能和可读性注意事项

在大多数实际应用中,.map()、.forEach() 与传统循环结构(如 for 或 while 循环)之间的性能差异通常很小。现代 JavaScript 引擎针对常见迭代模式进行了高度优化,能够高效执行这些操作。由于这些优化,选择这些方法通常更多是关于代码清晰度和意图,而非纯粹的性能。

像 .map() 这样的方法特别有用,因为它们清楚地表达了从现有数组创建一个新数组。当其他开发者阅读使用 .map() 的代码时,他们能快速理解目标是将原始数组的每个元素进行转换,并生成一个包含转换后值的新的数组。

例如,以下代码清楚地表明数组中的每个数字都被加倍:

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);

使用 .map() 使转换操作显式化,并避免了手动管理迭代过程。

传统的 for 循环也能实现相同结果,但通常需要更多样板代码:

const numbers = [1, 2, 3];
const doubled = [];

for (let i = 0; i < numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}

两个示例都会产生相同结果,但 .map() 版本更简洁,直接关注转换逻辑,而非循环的机械细节。

何时使用 .map()

当你想要:

  • 转换数组中的每个元素
  • 基于原始数组创建一个新数组
  • 对每个元素应用相同的操作
  • 将多个数组操作串联起来

何时使用 .forEach()

当你想要:

  • 为数组中的每个元素运行一个函数
  • 执行副作用,如记录值
  • 更新循环外部的变量
  • 执行不需要返回新数组的操作

性能注意事项

尽管 .map() 很方便,但需要注意以下几点性能问题:

  • .map() 总是创建一个新数组,这需要额外的内存。
  • 对于极大数据集,这种额外分配可能会有轻微的性能影响。
  • 传统循环在高度优化或性能关键的代码路径中,有时能提供略好的性能。

然而,在大多数应用中,这些差异可以忽略不计。开发者通常会优先考虑可读性和可维护性,而非微小的性能提升。

总之,在转换数组数据时,.map() 是首选方法。其简洁的语法和明确的意图使代码更容易理解和维护,尤其是在现代 JavaScript 应用中。

使用 .map() 的常见错误

尽管 .map() 是一个简单直接的方法,但初学者在使用它时常常会犯一些常见错误。理解这些错误可以帮助你避免意外结果并编写更清晰的代码。

忘记从回调函数返回值的错误

使用 .map() 时最常见的错误之一是忘记从回调函数中返回一个值。回调函数返回的值将成为新数组中对应的元素。

如果回调函数没有返回任何值,.map() 将为该元素插入 undefined。

例如:

const numbers = [1, 2, 3];

const doubled = numbers.map(num => {
  num * 2;
});

console.log(doubled);

这会产生以下输出:

输出
[ undefined, undefined, undefined ]

这是因为回调函数没有显式返回一个值。要修复这个问题,请返回转换后的值:

const doubled = numbers.map(num => {
  return num * 2;
});

你也可以使用箭头函数的隐式返回:

const doubled = numbers.map(num => num * 2);

在不需要返回数组时使用 .map()

另一个常见错误是在不使用它返回的数组时使用 .map()。因为 .map() 总是创建一个新数组,所以应该在打算转换数据并存储结果时使用它。

例如,这段代码错误地使用了 .map():

const numbers = [1, 2, 3];

numbers.map(num => {
  console.log(num);
});

虽然这段代码能工作,但返回的数组被忽略了。在这种情况下,.forEach() 通常是更好的选择:

numbers.forEach(num => {
  console.log(num);
});

在 .map() 中修改原始数组

另一个错误是在使用 .map() 时修改原始数组。.map() 的目的是基于现有数组的值创建一个新数组,而不是改变原始数据。

例如:

const numbers = [1, 2, 3];

const result = numbers.map((num, index, arr) => {
  arr[index] = num * 2;
  return num * 2;
});

虽然这段代码能工作,但它修改了原始数组,这可能会在大型应用中导致意外行为。

更好的方法是返回转换后的值而不更改原始数组:

const result = numbers.map(num => num * 2);

忘记 .map() 总是返回一个新数组

一些开发者期望 .map() 会修改原始数组。然而,.map() 总是返回一个新数组,并保持原始数组不变。

例如:

const numbers = [1, 2, 3];

numbers.map(num => num * 2);

console.log(numbers);

输出:

输出
[ 1, 2, 3 ]

要存储转换后的值,你必须将结果赋值给一个新变量:

const doubled = numbers.map(num => num * 2);

理解这些常见错误将帮助你更有效地使用 .map(),并在处理数组数据时避免隐蔽的 bug。

常见问题解答

1. JavaScript 中的 .map() 方法做什么?

.map() 方法遍历数组,并对每个元素应用一个回调函数。回调函数返回的值将成为 .map() 创建并返回的新数组中对应的元素。这使得 .map() 非常适合在保留原始数组的同时转换或重新格式化数据。

例如,你可以使用 .map() 来转换数字、从对象中提取属性,或在用户界面显示数据之前重塑数据。

2. .map() 会修改原始数组吗?

不会,.map() 不会修改原始数组。它是一个 非变异方法,意味着它返回一个包含转换后值的新的数组,同时保持原始数组不变。

但是,如果回调函数显式修改了原始数组的元素,这些更改仍然会发生。在大多数情况下,.map() 用于创建新的转换数组,而不更改原始数据。

3. .map() 和 .forEach() 有什么区别?

.map() 和 .forEach() 都会遍历数组中的元素并为每个项目执行回调函数。主要区别在于 .map() 返回一个新数组,而 .forEach() 不返回值。

当你想转换数组中的每个元素并将结果存储在新数组中时,使用 .map()。当你只是想对每个元素执行操作,例如记录值、更新变量或触发副作用时,使用 .forEach()。

4. 我可以在对象上使用 .map() 吗?

.map() 设计用于数组和类数组值,因此不能直接用于普通的 JavaScript 对象。

如果你需要对对象数据应用 .map(),可以使用 Object.keys()、Object.values() 或 Object.entries() 等方法先将对象的键或值转换为数组。一旦数据以数组形式存在,你就可以使用 .map() 来转换它。

5. 什么时候应该使用 .map() 而不是 .filter()?

当你想转换数组中的每个元素并生成一个包含转换后值的新的数组时,使用 .map()。例如,你可以使用 .map() 来将数字加倍、从对象中提取属性,或将值转换为不同的格式。

当你想根据条件从数组中选择某些元素时,使用 .filter()。它不会转换值,而是返回一个只包含满足指定条件的元素的新数组。

结论

在本教程中,你探索了 JavaScript 中 .map() 方法的几种实际用法。你学习了如何转换数组值、将字符串转换为数组、在 JavaScript 库中渲染列表,以及重新格式化对象数组。

你还比较了 .map() 与相关数组方法如 .forEach() 和 .filter(),学习了如何将数组方法链式组合,以及回顾了常见错误,例如忘记从回调函数返回值的错误。

理解何时以及如何使用 .map() 可以帮助你编写更清晰、更具表现力的 JavaScript 代码。有关更多 JavaScript 相关教程,请查看以下文章:

  • How To Use Array Methods in JavaScript: Mutator Methods
  • How To Use Array Methods in JavaScript: Iteration Methods
  • Four Methods to Search Through Arrays in JavaScript
  • How To Work with JSON in JavaScript