当前位置: 首页 > news >正文

【JavaScript-Day 28】告别繁琐循环:`forEach`, `map`, `filter` 数组遍历三剑客详解

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

01-【JavaScript-Day 1】从零开始:全面了解 JavaScript 是什么、为什么学以及它与 Java 的区别
02-【JavaScript-Day 2】开启 JS 之旅:从浏览器控制台到 <script> 标签的 Hello World 实践
03-【JavaScript-Day 3】掌握JS语法规则:语句、分号、注释与大小写敏感详解
04-【JavaScript-Day 4】var 完全指南:掌握变量声明、作用域及提升
05-【JavaScript-Day 5】告别 var 陷阱:深入理解 letconst 的妙用
06-【JavaScript-Day 6】从零到精通:JavaScript 原始类型 String, Number, Boolean, Null, Undefined, Symbol, BigInt 详解
07-【JavaScript-Day 7】全面解析 Number 与 String:JS 数据核心操作指南
08-【JavaScript-Day 8】告别混淆:一文彻底搞懂 JavaScript 的 Boolean、null 和 undefined
09-【JavaScript-Day 9】从基础到进阶:掌握 JavaScript 核心运算符之算术与赋值篇
10-【JavaScript-Day 10】掌握代码决策核心:详解比较、逻辑与三元运算符
11-【JavaScript-Day 11】避坑指南!深入理解JavaScript隐式和显式类型转换
12-【JavaScript-Day 12】掌握程序流程:深入解析 if…else 条件语句
13-【JavaScript-Day 13】告别冗长if-else:精通switch语句,让代码清爽高效!
14-【JavaScript-Day 14】玩转 for 循环:从基础语法到遍历数组实战
15-【JavaScript-Day 15】深入解析 while 与 do…while 循环:满足条件的重复执行
16-【JavaScript-Day 16】函数探秘:代码复用的基石——声明、表达式与调用详解
17-【JavaScript-Day 17】函数的核心出口:深入解析 return 语句的奥秘
18-【JavaScript-Day 18】揭秘变量的“隐形边界”:深入理解全局与函数作用域
19-【JavaScript-Day 19】深入理解 JavaScript 作用域:块级、词法及 Hoisting 机制
20-【JavaScript-Day 20】揭秘函数的“记忆”:深入浅出理解闭包(Closure)
21-【JavaScript-Day 21】闭包实战:从模块化到内存管理,高级技巧全解析
22-【JavaScript-Day 22】告别 function 关键字?ES6 箭头函数 (=>) 深度解析
23-【JavaScript-Day 23】告别繁琐的参数处理:玩转 ES6 默认参数与剩余参数
24-【JavaScript-Day 24】从零到一,精通 JavaScript 对象:创建、访问与操作
25-【JavaScript-Day 25】深入探索:使用 for...in 循环遍历 JavaScript 对象属性
26-【JavaScript-Day 26】零基础掌握JavaScript数组:轻松理解创建、索引、长度和多维结构
27-【JavaScript-Day 27】玩转数组:push, pop, slice, splice 等方法详解与实战
28-【JavaScript-Day 28】告别繁琐循环:forEach, map, filter 数组遍历三剑客详解


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • 前言
  • 一、为什么需要专门的数组遍历方法?
    • 1.1 `for` 循环的“痛点”
    • 1.2 声明式编程的优势
  • 二、`forEach()`:为每个元素执行一次“任务”
    • 2.1 基本语法与工作原理
    • 2.2 `forEach()` 的特点
    • 2.3 应用场景与实例
      • 2.3.1 打印数组元素
      • 2.3.2 计算数组元素的总和
      • 2.3.3 修改对象数组中对象的属性
    • 2.4 注意事项
  • 三、`map()`:将数组“映射”成新数组
    • 3.1 基本语法与工作原理
    • 3.2 `map()` 与 `forEach()` 的核心区别
    • 3.3 应用场景与实例
      • 3.3.1 提取对象数组中的特定属性
      • 3.3.2对数组中的每个数字进行运算
      • 3.3.3 格式化数据
    • 3.4 注意事项
  • 四、`filter()`:筛选符合条件的“合格者”
    • 4.1 基本语法与工作原理
    • 4.2 `filter()` 的强大之处
    • 4.3 应用场景与实例
      • 4.3.1 筛选出数组中的偶数
      • 4.3.2 筛选出对象数组中符合特定条件的项
      • 4.3.3 移除数组中的无效值
    • 4.4 注意事项
  • 五、`forEach()`, `map()`, `filter()` 对比总结
    • 5.1 使用场景建议
  • 六、总结


前言

在 JavaScript 编程中,数组是最常用的数据结构之一。高效地遍历和操作数组是每个开发者必备的技能。传统的 for 循环虽然能够完成任务,但在许多场景下显得冗余且不够直观。ES5 引入了一系列强大的数组迭代方法,它们不仅能让代码更简洁、易读,还能以更声明式的方式表达我们的意图。本篇将深入探讨其中三个最核心的遍历方法:forEach()map()filter(),帮助你理解它们的原理、应用场景及注意事项,从而提升你的 JavaScript 编程水平。


一、为什么需要专门的数组遍历方法?

在深入学习这些新方法之前,我们有必要先理解为什么需要它们,以及它们相比传统 for 循环的优势。

1.1 for 循环的“痛点”

经典的 for 循环是我们最早接触的循环结构之一:

const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {console.log(arr[i]); // 访问当前元素// 执行其他操作...
}

虽然 for 循环功能强大且灵活,但在某些方面存在不足:

  • 代码冗余: 每次遍历都需要显式声明索引变量 (i)、设置循环条件 (i < arr.length) 和更新索引 (i++)。
  • 易出错: 手动管理索引容易导致边界错误(如 i <= arr.length 导致的越界)。
  • 不够语义化: 代码的意图不够清晰,阅读者需要仔细分析循环体才能理解其目的(是遍历、修改还是筛选?)。

1.2 声明式编程的优势

forEach(), map(), filter() 等方法属于声明式编程的范畴。与命令式编程(如 for 循环,详细描述“如何做”)不同,声明式编程更侧重于“做什么”,而不是“如何做”。

这种转变带来的好处是:

  • 代码更简洁: 省去了索引管理等模板代码。
  • 可读性更高: 方法名本身就清晰地表达了操作的目的(如 filter 就是为了筛选)。
  • 关注点分离: 将遍历机制封装在方法内部,开发者只需关注每个元素需要执行的具体操作。
  • 易于维护: 结构更清晰,修改和调试更方便。

接下来,让我们逐一认识这三个强大的数组迭代器。


二、forEach():为每个元素执行一次“任务”

forEach() 方法对数组的每个元素执行一次提供的函数(回调函数)。它更侧重于“遍历”这个动作本身,通常用于执行某些副作用,比如打印日志、修改外部变量或更新 DOM。

2.1 基本语法与工作原理

forEach() 的语法如下:

array.forEach(function(currentValue, index, arr), thisValue)
  • callbackFn: 为数组中每个元素执行的函数,它接收三个参数:
    • currentValue: 数组中正在处理的当前元素。
    • index (可选): 数组中正在处理的当前元素的索引。
    • arr (可选): forEach() 方法正在操作的数组。
  • thisArg (可选): 执行 callbackFn 时用作 this 的值。

工作原理简述:
forEach() 会按升序对数组中的每个有效元素调用一次 callbackFn。它不会对已删除或未初始化的项调用回调函数(即稀疏数组中的空位会被跳过)。

2.2 forEach() 的特点

  • 无返回值: forEach() 方法总是返回 undefined。它不创建新数组,主要用于数组元素的迭代处理。
  • 无法中途停止: 与 for 循环中的 break 不同,你不能通过在回调函数中返回 falsebreak 来终止 forEach() 的迭代。如果需要提前终止,可以考虑使用 for...ofevery()some()find(),或者将 forEach 放在一个 try...catch 块中并抛出异常(但不推荐这种方式来控制流程)。
  • 不直接修改原数组: forEach() 本身不修改调用它的数组。但是,回调函数可以修改原数组。如果回调函数修改了原数组,那么后续遍历的元素可能会受到影响。

2.3 应用场景与实例

2.3.1 打印数组元素

最常见的用途是遍历并对每个元素执行操作,如打印:

const fruits = ['apple', 'banana', 'cherry'];fruits.forEach(function(fruit, index) {console.log(`Index ${index}: ${fruit}`);
});
// 输出:
// Index 0: apple
// Index 1: banana
// Index 2: cherry

2.3.2 计算数组元素的总和

虽然 reduce 更适合此场景,但 forEach 也可以实现:

const numbers = [10, 20, 30, 40];
let sum = 0;numbers.forEach(function(number) {sum += number; // 修改外部变量 sum
});console.log(sum); // 输出: 100

2.3.3 修改对象数组中对象的属性

const users = [{ id: 1, name: 'Alice', isActive: false },{ id: 2, name: 'Bob', isActive: false },{ id: 3, name: 'Charlie', isActive: false }
];// 激活所有用户
users.forEach(user => {user.isActive = true; // 直接修改原数组中对象的属性
});console.log(users);
// 输出:
// [
//   { id: 1, name: 'Alice', isActive: true },
//   { id: 2, name: 'Bob', isActive: true },
//   { id: 3, name: 'Charlie', isActive: true }
// ]

2.4 注意事项

  • this 指向: 如果回调函数是普通函数,在非严格模式下其 this 指向全局对象(浏览器中是 window),严格模式下是 undefined。可以使用 thisArg 参数来指定回调函数中 this 的值,或者使用箭头函数(箭头函数不绑定自己的 this,它会捕获其所在上下文的 this 值)。
    const myObj = {prefix: 'Item: ',logItems: function(items) {items.forEach(function(item) {// console.log(this.prefix + item); // 报错,this 不是 myObj}, this); // 传入 this 作为 thisArgitems.forEach(item => {console.log(this.prefix + item); // 箭头函数,this 继承自 logItems});}
    };
    myObj.logItems(['A', 'B']);
    // 输出 (使用 thisArg):
    // Item: A
    // Item: B
    // 输出 (使用箭头函数):
    // Item: A
    // Item: B
    
  • 稀疏数组: forEach() 会跳过稀疏数组中的空位。
    const sparseArray = [1, , 3]; // 注意中间的空位
    sparseArray.forEach(val => console.log(val));
    // 输出:
    // 1
    // 3
    // (索引 1 被跳过)
    

三、map():将数组“映射”成新数组

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。它非常适合于需要转换数组数据结构的场景。

3.1 基本语法与工作原理

map() 的语法与 forEach() 类似:

const new_array = arr.map(function(currentValue, index, arr), thisArg)
  • callbackFn: 为数组中每个元素执行的函数,同样接收 currentValue, index, arr 三个参数。关键在于,这个回调函数应该返回一个值,这个值将作为新数组中对应位置的元素。
  • thisArg (可选): 执行 callbackFn 时用作 this 的值。

工作原理简述:
map() 方法会遍历原始数组的每个元素,对每个元素执行 callbackFn,并将每次 callbackFn 执行的返回值收集起来,组成一个新的数组。原始数组不会被改变。

3.2 map()forEach() 的核心区别

最核心的区别在于:

  • 返回值: map() 返回一个新的数组,这个新数组的长度与原数组相同。forEach() 返回 undefined
  • 用途: map() 主要用于“转换”或“映射”数据,即根据原数组生成一个结构或内容上有所不同的新数组。forEach() 主要用于执行某种操作或副作用,不关心返回值。

3.3 应用场景与实例

3.3.1 提取对象数组中的特定属性

假设我们有一个用户对象数组,我们想提取所有用户的姓名组成一个新数组:

const users = [{ id: 1, name: 'Alice', age: 30 },{ id: 2, name: 'Bob', age: 25 },{ id: 3, name: 'Charlie', age: 35 }
];const names = users.map(user => user.name);
console.log(names); // 输出: ['Alice', 'Bob', 'Charlie']
console.log(users); // 原数组 users 未改变

3.3.2对数组中的每个数字进行运算

const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(number => number * 2);console.log(doubledNumbers); // 输出: [2, 4, 6, 8, 10]

3.3.3 格式化数据

将一组数字转换为包含其原始值和格式化字符串的对象数组:

const prices = [19.99, 25, 10.50];
const formattedPrices = prices.map(price => {return {original: price,display: `$${price.toFixed(2)}` // toFixed(2) 保留两位小数};
});console.log(formattedPrices);
// 输出:
// [
//   { original: 19.99, display: '$19.99' },
//   { original: 25, display: '$25.00' },
//   { original: 10.5, display: '$10.50' }
// ]

3.4 注意事项

  • 回调函数的返回值: 如果回调函数没有 return 语句,或者 return 了一个 undefined,那么新数组的对应项也会是 undefined
    const numbers = [1, 2, 3];
    const mappedUndefined = numbers.map(num => {console.log(num); // 只有副作用,没有返回值
    });
    console.log(mappedUndefined); // 输出: [undefined, undefined, undefined]
    
  • 新数组的长度: map() 返回的新数组长度总是和原数组长度一致,即使回调函数返回 undefined
  • 不修改原数组: map() 始终返回一个新数组,不会修改原数组(除非回调函数内部有修改原数组的副作用,但这通常不推荐)。

四、filter():筛选符合条件的“合格者”

filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。简单来说,它像一个过滤器,只保留那些符合特定条件的元素。

4.1 基本语法与工作原理

filter() 的语法也与前两者类似:

const new_array = arr.filter(function(currentValue, index, arr), thisArg)
  • callbackFn: 为数组中每个元素执行的函数,也接收 currentValue, index, arr 三个参数。关键在于,这个回调函数(通常称为“断言函数”或 “predicate”)应该返回一个布尔值。如果返回 true,则当前元素会被包含在新数组中;如果返回 false,则不会。
  • thisArg (可选): 执行 callbackFn 时用作 this 的值。

工作原理简述:
filter() 方法会遍历原始数组的每个元素,对每个元素执行 callbackFn。如果 callbackFn 对某个元素返回 true,该元素就会被添加到新数组中。如果返回 false,则该元素被忽略。原始数组不会被改变。

4.2 filter() 的强大之处

filter() 的强大之处在于它能以非常声明式的方式从数组中提取子集,使得代码意图清晰明了。

4.3 应用场景与实例

4.3.1 筛选出数组中的偶数

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter(number => number % 2 === 0); // 回调返回 true 则保留console.log(evenNumbers); // 输出: [2, 4, 6, 8, 10]

4.3.2 筛选出对象数组中符合特定条件的项

假设我们有一个产品列表,需要筛选出价格低于 $50 的产品:

const products = [{ id: 1, name: 'Laptop', price: 1200 },{ id: 2, name: 'Mouse', price: 25 },{ id: 3, name: 'Keyboard', price: 75 },{ id: 4, name: 'Webcam', price: 45 }
];const affordableProducts = products.filter(product => product.price < 50);console.log(affordableProducts);
// 输出:
// [
//   { id: 2, name: 'Mouse', price: 25 },
//   { id: 4, name: 'Webcam', price: 45 }
// ]

4.3.3 移除数组中的无效值

例如,移除数组中的 nullundefined

const mixedValues = [0, 1, null, 'hello', undefined, false, NaN, ''];
const validValues = mixedValues.filter(value => value !== null && value !== undefined);
// 更简洁的方式,利用 Boolean 转换:
// const validValues = mixedValues.filter(Boolean); // 但要注意 0, false, NaN, '' 也会被过滤掉console.log(validValues); // 输出 (使用第一种方式): [0, 1, 'hello', false, NaN, '']

注意: 如果使用 filter(Boolean),像 0, false, NaN, '' 这样的假值 (falsy values) 也会被过滤掉。如果只想移除 nullundefined,则需要显式判断。

4.4 注意事项

  • 回调函数的返回值: 回调函数必须返回一个布尔值(或者可以被强制转换为布尔值的值)。如果返回 true,元素保留;false 则舍弃。
  • 新数组的长度: filter() 返回的新数组长度通常小于或等于原数组长度。
  • 不修改原数组: filter() 始终返回一个新数组,不会修改原数组。

五、forEach(), map(), filter() 对比总结

为了更清晰地理解这三者,我们可以通过一个表格进行对比:

特性forEach()map()filter()
主要目的遍历数组,对每个元素执行操作(通常是副作用)遍历数组,对每个元素执行操作,并用返回值构建一个新数组遍历数组,对每个元素执行测试,用通过测试的元素构建一个新数组
返回值undefined一个新数组,长度与原数组相同一个新数组,长度小于或等于原数组
是否修改原数组本身不修改,但回调函数可以修改本身不修改,返回新数组(回调函数若修改原数组则不推荐)本身不修改,返回新数组
回调函数返回值通常不使用其返回值非常重要,作为新数组的元素非常重要,作为判断是否保留元素的依据(true 保留, false 舍弃)
常见用例打印日志、更新UI、修改对象属性等副作用数据转换(如提取对象属性、格式化数据)、生成派生数据数据筛选(如移除无效项、选取符合条件的对象)
能否链式调用 (基于返回值)否 (因为返回 undefined)是 (可以接 filter(), forEach(), reduce() 等)是 (可以接 map(), forEach(), reduce() 等)

5.1 使用场景建议

  • 当你只需要遍历数组执行某些操作,不关心返回值,也不需要生成新数组时,使用 forEach()
    • 例如:为数组中的每个 DOM 元素添加一个事件监听器。
  • 当你需要根据原数组的每个元素生成一个对应的新元素,并得到一个同样长度的新数组时,使用 map()
    • 例如:将一个数字数组中的每个数字平方后得到一个新数组。
    • 例如:从一个对象数组中提取每个对象的某个属性值组成一个新数组。
  • 当你需要从原数组中筛选出一部分符合特定条件的元素,并得到一个可能更短的新数组时,使用 filter()
    • 例如:从一个用户列表中筛选出所有已激活的用户。
    • 例如:移除数组中的所有负数。

一个综合例子:

假设我们有一个用户列表,我们想得到所有年龄大于等于18岁的用户的姓名,并转换为大写。

const users = [{ name: 'Alice', age: 25 },{ name: 'Bob', age: 17 },{ name: 'Charlie', age: 30 },{ name: 'David', age: 15 }
];const adultUserNamesUpperCase = users.filter(user => user.age >= 18) // 1. 筛选出成年用户.map(user => user.name.toUpperCase()); // 2. 提取姓名并转为大写console.log(adultUserNamesUpperCase); // 输出: ["ALICE", "CHARLIE"]

这个例子清晰地展示了 filter()map() 的链式调用,代码简洁且易于理解。


六、总结

本篇文章详细介绍了 JavaScript 中三个核心的数组遍历方法:forEach()map()filter()。掌握它们对于编写更现代、更具可读性和可维护性的 JavaScript 代码至关重要。

核心要点回顾:

  1. forEach(callbackFn):

    • 用途: 遍历数组,为每个元素执行回调函数,常用于执行副作用。
    • 返回值: undefined
    • 特点: 不能直接中止循环(除非抛异常),回调函数不改变 forEach 的行为。
  2. map(callbackFn):

    • 用途: 遍历数组,将每个元素传递给回调函数,并使用回调函数的返回值创建一个新数组
    • 返回值: 一个新的、经过转换的数组,长度与原数组相同。
    • 特点: 核心在于“映射”和“转换”,原数组不变。
  3. filter(callbackFn):

    • 用途: 遍历数组,将每个元素传递给回调函数(断言函数),如果回调返回 true,则该元素被包含在新数组中。
    • 返回值: 一个新的、经过筛选的数组,长度可能小于或等于原数组。
    • 特点: 核心在于“筛选”和“选择”,原数组不变。
  4. 选择合适的迭代器: 根据你的具体需求——是仅遍历、转换数据还是筛选数据——选择最语义化的方法,可以让你的代码意图更清晰。

这些迭代方法不仅让代码更优雅,也是函数式编程思想在 JavaScript 中的体现。在下一篇文章中,我们将继续探讨更多强大的数组迭代方法,如 reduce()find()some()every() 等,它们将进一步拓展你处理数组的能力。敬请期待!


相关文章:

  • Selenium Manager中文文档
  • Python-Selenium报错截图
  • hysAnalyser --- 逐包分析MPEG-TS的功能说明
  • 图文详解Java集合面试题
  • 量化面试绿皮书:1. 海盗分金博弈
  • 树莓派3B小练习
  • 【JMeter】性能测试知识和工具
  • Spring AI Image Model、TTS,RAG
  • 区块链可投会议CCF B--EDBT 2026 截止10.8 附录用率
  • 基于React + TypeScript构建高度可定制的QR码生成器
  • Codeforces Round 1028 (Div. 2) C. Gellyfish and Flaming Peony
  • MCU SoC
  • 【Dv3Admin】系统视图系统配置API文件解析
  • 举办中国户外运动展哪个城市较理想
  • Vert.x学习笔记-EventLoop与Context的关系
  • LTSPICE仿真电路:(三十二)差动放大器电流源
  • RPA办公自动化工具初体验
  • 使用 DeepSeek API 搭建智能体《无间》- 卓伊凡的完整指南 -优雅草卓伊凡
  • 【Oracle】数据备份与恢复
  • “候选对话链”(Candidate Dialogue Chain)概念
  • 云盘可以做网站吗/google国际版
  • wordpress 返利网/江门seo外包公司
  • 杭州网站建设制作/百度移动端模拟点击排名
  • jsp做的网站怎嘛用/品牌策划公司介绍
  • 石家庄做淘宝网站/新网域名
  • 爱唐山做贡献月评十佳投票网站/培训网站推荐