前端开发中的常用工具函数(十):JavaScript 数组遍历

目录

[一、 传统 for 循环:一切的基石](#一、 传统 for 循环:一切的基石)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[二、 forEach():朴实无华的执行者](#二、 forEach():朴实无华的执行者)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[三、 for...of:现代异步遍历之王 (ES6)](#三、 for...of:现代异步遍历之王 (ES6))

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[四、 map():数据映射器](#四、 map():数据映射器)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[五、 filter():数据过滤器](#五、 filter():数据过滤器)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[六、 reduce():全能聚合器](#六、 reduce():全能聚合器)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[七、 some() 和 every():布尔值质检员](#七、 some() 和 every():布尔值质检员)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项](#4. 注意事项)

[八、 for...in:请勿用于数组遍历](#八、 for...in:请勿用于数组遍历)

[1. 语法](#1. 语法)

[2. 参数介绍](#2. 参数介绍)

[3. 适用业务场景](#3. 适用业务场景)

[4. 注意事项(针对数组)](#4. 注意事项(针对数组))

总结:如何选择?

在前端开发中,我们每天都在和数组打交道。从渲染列表数据、处理接口返回的 JSON,到复杂的表单校验,几乎 80% 的业务逻辑都离不开数组遍历。

JavaScript 为我们提供了极其丰富的数组遍历方法。

一、 传统 for 循环:一切的基石

最原始、最硬核的遍历方式。通过控制索引来访问数组元素。

1. 语法

javascript 复制代码
for (let i = 0; i < arr.length; i++) {
  // 执行代码 arr[i]
}

2. 参数介绍

  • i:循环控制变量(索引)。
  • arr.length:数组的长度,作为循环的边界条件。

3. 适用业务场景

  • 复杂的索引计算 :比如需要跳跃式遍历(i += 2),或者需要倒序遍历数组。
  • 性能极致要求 :在处理超大型数组(如十万级以上数据)时,for 循环的执行速度通常是最快的。

4. 注意事项

  • 易引发差一错误 :需要手动维护边界条件(< 还是 <= 极易写错),导致越界。
  • 作用域问题 :早期使用 var 声明 i 会导致变量泄露到全局,现代开发必须使用 let 声明。
  • 优点 :极其灵活,可以通过 break 跳出循环,通过 continue 跳过本次循环;可以倒序遍历。
  • 缺点:语法较为繁琐,需要手动维护索引,容易发生越界错误(差一错误)。
javascript 复制代码
const arr = ['Vue', 'React', 'Angular'];

for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);
}

二、 forEach():朴实无华的执行者

数组原生的遍历方法,它接收一个回调函数,对数组中的每个元素执行一次。

1. 语法

javascript 复制代码
array.forEach(function(currentValue, index, array), thisArg)

2. 参数介绍

  • currentValue:数组中当前正在处理的元素。
  • index(可选):当前元素在数组中的索引。
  • array(可选):正在遍历的数组本身(极少用到)。
  • thisArg(可选):执行回调时用作 this 的值。

3. 适用业务场景

  • 纯粹的副作用操作:例如遍历 DOM 节点列表批量添加事件监听,或者遍历日志数组批量打印。
  • 修改对象数组属性 :例如遍历用户列表,给每个用户对象动态添加一个 isChecked: false 属性。

4. 注意事项

  • 无法中途跳出 :不能用 breakcontinue,会直接报语法错误。
  • 不支持 await :如果回调函数中有异步请求,forEach 不会等待异步完成,容易导致数据不同步。
  • 无法 return 终止外层函数 :在 forEachreturn 只是结束当前回调,相当于 for 循环里的 continue
javascript 复制代码
const arr = ['Vue', 'React', 'Angular'];

arr.forEach((item, index, array) => {
  console.log(index, item);
  // array 就是原数组 arr
});

三、 for...of:现代异步遍历之王 (ES6)

for...of 是 ES6 引入的语法,用于遍历可迭代对象(包括数组、Map、Set、字符串等,但不包括普通对象)。

1. 语法

javascript 复制代码
for (let variable of iterable) {
  // 执行代码
}

2. 参数介绍

  • variable:每次迭代时,接收来自 iterable 的值。
  • iterable:被遍历的可迭代对象。

3. 适用业务场景

  • 包含异步操作的遍历 :例如需要按顺序请求多个接口并处理结果,for...of 完美支持 await
  • 需要提前终止的循环 :遇到特定条件需要 break 跳出,或 continue 跳过。
  • 遍历 Map 或 Set 结构:这是最直观、最推荐的方式。

4. 注意事项

  • 不遍历普通对象 :普通对象默认不是可迭代的,如果需要遍历对象的键值对,请使用 Object.entries(obj) 配合 for...of
  • 拿索引稍显繁琐 :如果业务逻辑重度依赖索引,需要配合 arr.entries() 解构使用:for (const [index, item] of arr.entries())
    • 优点
      1. 语法最简洁直观,直接拿到值。
      2. 支持 breakcontinuereturn
      3. 完美支持 await :如果在 async 函数中,for...of 会等待上一个异步操作完成再进行下一次循环。
javascript 复制代码
const arr = ['Vue', 'React', 'Angular'];

for (const item of arr) {
  console.log(item); // 直接拿到值,不需要写 arr[index]
  
  if (item === 'React') break; // ✅ 可以正常跳出
}

// 如果需要索引,可以结合 entries()
for (const [index, item] of arr.entries()) {
  console.log(index, item);
}

四、 map():数据映射器

map 的意思是"映射"。它遍历数组,对每一项执行回调函数,并返回一个全新的数组。新数组的长度和原数组一致。

1. 语法

javascript 复制代码
let newArray = array.map(function(currentValue, index, array), thisArg)

2. 参数介绍

  • currentValue:当前处理的元素。
  • index(可选):当前元素的索引。
  • array(可选):原数组。
  • thisArg(可选):执行回调时用作 this 的值。
  • 返回值:一个由回调函数返回值组成的新数组。

3. 适用业务场景

  • 数据结构转换:将后端返回的数据结构转换为前端组件(如 Vue/React 组件)需要的数据结构。
  • 提取属性 :从对象数组中提取某个属性组成新数组,如 users.map(user => user.name)

4. 注意事项

  • 必须有返回值 :如果回调函数里忘记 return,新数组里会全是 undefined
  • 不可变性map 不会改变原数组,但它返回的是浅拷贝。如果数组元素是对象,在新数组中修改对象属性,会影响原数组中的对象。
  • 优点:函数式编程风格,不修改原数组(不可变性),链式调用极其方便。
  • 缺点 :如果不使用返回值,用 map 就是浪费内存。
javascript 复制代码
const userList = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
];

// 提取所有用户的姓名组成新数组
const names = userList.map(user => user.name);
console.log(names); // ['张三', '李四']

⚠️ 注意 :很多人错把 map 当成 forEach 用,如果不需要返回新数组,请用 forEachfor...of

五、 filter():数据过滤器

顾名思义,filter 用于过滤数组。它遍历数组的每一项,回调函数返回 true 的项会被保留,最终返回一个新数组

1. 语法

javascript 复制代码
let newArray = array.filter(function(currentValue, index, array), thisArg)

2. 参数介绍

  • currentValue:当前处理的元素。
  • index(可选):当前元素的索引。
  • array(可选):原数组。
  • 返回值 :一个通过测试的新数组。如果没有通过测试的元素,返回空数组 []

3. 适用业务场景

  • 搜索过滤:前端实现模糊搜索,过滤出包含关键字的数据。
  • 条件筛选:如从订单列表中筛选出"已付款"状态的订单。

4. 注意事项

  • 返回布尔值 :回调函数内部必须返回一个条件表达式(真值或假值),如果无脑 return true 则相当于深拷贝。
  • 空数组处理filter 不会对空数组进行检测。
    • 优点:语义清晰,专门用于筛选数据,保持原数组不变。
javascript 复制代码
const numbers = [1, 2, 3, 4, 5, 6];

// 找出所有偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]

六、 reduce():全能聚合器

reduce 是数组方法中最强大、但也最常被初学者忽视的方法。它接收一个回调函数和一个初始值,遍历数组将每一项"累积"处理,最终返回一个单一的值。

1. 语法

javascript 复制代码
let result = array.reduce(function(accumulator, currentValue, index, array), initialValue)

2. 参数介绍

  • accumulator (累计器):上一次回调函数的返回值,或者是初始值 initialValue
  • currentValue:当前处理的元素。
  • index(可选):当前索引。如果提供了 initialValue,从 0 开始;否则从 1 开始。
  • array(可选):原数组。
  • initialValue(强烈建议提供):作为第一次调用回调函数时的第一个参数的值。

3. 适用业务场景

  • 统计计算:计算购物车商品总价、统计数组中各个元素出现的次数。
  • 数组转对象 :将 [{id: 1, name: '张三'}] 转换为 {1: {name: '张三'}} 方便快速查找。
  • 多维数组扁平化:配合空数组作为初始值,将多维数组拍平。

4. 注意事项

  • 一定要传初始值 :如果不传 initialValuereduce 会默认使用数组的第一个元素作为初始值,并跳过第一次循环。如果数组为空且未提供初始值,会直接报错 Reduce of empty array with no initial value
  • 必须返回累计器 :回调函数最后必须 return accumulator(或更新后的累计值),否则下一次循环拿到的就是 undefined
    • 优点 :功能极强,可以做求和、扁平化数组、分组、甚至实现 mapfilter 的组合功能。
javascript 复制代码
const cart = [
  { name: '键盘', price: 200 },
  { name: '鼠标', price: 100 },
  { name: '显示器', price: 1000 }
];

// 计算购物车总价
const totalPrice = cart.reduce((acc, item) => acc + item.price, 0);
console.log(totalPrice); // 1300

// 进阶:将数组按属性分组
const grouped = cart.reduce((acc, item) => {
  const type = item.price > 500 ? '贵' : '便宜';
  if (!acc[type]) acc[type] = [];
  acc[type].push(item);
  return acc;
}, {});

七、 some()every():布尔值质检员

这两个方法用于判断数组中的元素是否满足某种条件,返回值为布尔值(true / false)。它们非常高效,因为一旦能确定结果,就会提前终止遍历

  • some() :只要有一项满足条件,就返回 true(相当于逻辑或 ||)。
  • every() :必须所有项都满足条件,才返回 true(相当于逻辑与 &&)。

1. 语法

javascript 复制代码
let boolean = array.some(function(currentValue, index, array), thisArg)
let boolean = array.every(function(currentValue, index, array), thisArg)

2. 参数介绍

  • currentValue:当前元素。
  • index(可选):当前索引。
  • array(可选):原数组。
  • 返回值truefalse

3. 适用业务场景

  • some (相当于逻辑或 ||):判断数组中是否"存在"满足条件的元素。例如:判断用户列表里是否有管理员权限(只要有一个就返回 true)。
  • every (相当于逻辑与 &&):判断数组是否"全员满足"条件。例如:提交表单前校验所有必填项是否都填写完毕。
  • 适用场景:表单全量校验、权限校验(判断用户是否拥有某个权限标识)。

4. 注意事项

  • 短路特性some 找到第一个符合条件的就会停止遍历并返回 trueevery 找到第一个不符合条件的就会停止遍历并返回 false。利用这个特性可以提升性能。
  • 空数组的表现 :对于空数组,some 返回 false,而 every 返回 true(数学上的逻辑,因为不存在不满足条件的项)。
javascript 复制代码
const scores = [80, 90, 60, 75];

// 检查是否有不及格的(只要有一个 < 60 就返回 true)
const hasFail = scores.some(score => score < 60);
console.log(hasFail); // true

// 检查是否全员都及格(必须所有 >= 60 才返回 true)
const isAllPass = scores.every(score => score >= 60);
console.log(isAllPass); // false

八、 for...in:请勿用于数组遍历

虽然 for...in 也能遍历数组,但它是为遍历对象的枚举属性而设计的。如果用在数组上,会带来隐患

1. 语法

javascript 复制代码
for (let key in object) {
  // 执行代码
}

2. 参数介绍

  • key:对象属性的键名(在数组中是字符串形式的索引 "0", "1")。
  • object:被遍历的对象。

3. 适用业务场景

  • 仅用于遍历普通对象的键值对

4. 注意事项(针对数组)

  • 会遍历自定义属性 :如果你给数组对象加了自定义属性 arr.name = 'test'for...in 会把它也遍历出来。
  • 顺序不可靠:在某些旧引擎下,遍历顺序可能不按照数组的物理顺序。
  • 性能差:因为它需要去查找原型链上的可枚举属性。
  • 结论永远不要用 for...in 遍历数组!
  • 缺点
    1. 遍历的是键名(字符串形式的索引),不是值。
    2. 会遍历出数组对象自定义的可枚举属性。
    3. 遍历顺序不保证(某些旧引擎下)。
javascript 复制代码
const arr = ['a', 'b', 'c'];
arr.customProp = 'hello'; // 给数组加了自定义属性

for (const key in arr) {
  console.log(key); // '0', '1', '2', 'customProp' ❌ 遍历出了索引和自定义属性!
}

总结:如何选择?

面对这么多方法,记住下面这张决策树,保证你不会选错:

  1. 我想把数组变成另一种形态的新数组 ? ➡️ map()
  2. 我想筛选出符合条件的新数组 ? ➡️ filter()
  3. 我想把数组合并成一个值 (求和、转对象)? ➡️ reduce()
  4. 我只想知道数组里有没有某项 满足条件? ➡️ some() / every()
  5. 我想对每一项执行副作用(打印、修改外部变量),且没有异步 ? ➡️ forEach()
  6. 循环里有 async/await ,或者需要 break 跳出 ? ➡️ for...of
  7. 需要极其复杂的索引控制或追求极致性能? ➡️ 传统 for 循环
相关推荐
FYKJ_20101 小时前
django学习成绩预警系统10905
java·javascript·spring boot·python·spark·django·php
云浪1 小时前
给大模型装上"手":三分钟看懂 AI Agent 的 Tool 工具调用
javascript·人工智能·node.js
风月说与山鬼1 小时前
六、React事件
前端·javascript·react.js
岁岁种桃花儿1 小时前
Vue核心语法第十四篇:列表过滤
前端·javascript·vue.js
晓得迷路了1 小时前
栗子前端技术周刊第 143 期 - npm 攻击事件、Bun 1.4...
前端·javascript
常宇佳1 小时前
vue3 实战系列之踩过的一些坑
javascript·vue.js·elementui
研☆香14 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
日光倾16 小时前
TypeScript 随手记 —— 1
前端·javascript·typescript
Buke..19 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(下):OLLVM 混淆还原 sign 算法
java·javascript·爬虫·python·算法