目录
[一、 传统 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. 注意事项
- 无法中途跳出 :不能用
break或continue,会直接报语法错误。 - 不支持
await:如果回调函数中有异步请求,forEach不会等待异步完成,容易导致数据不同步。 - 无法
return终止外层函数 :在forEach里return只是结束当前回调,相当于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())。
-
- 优点 :
- 语法最简洁直观,直接拿到值。
- 支持
break、continue和return。 - 完美支持
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 用,如果不需要返回新数组,请用 forEach 或 for...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. 注意事项
- 一定要传初始值 :如果不传
initialValue,reduce会默认使用数组的第一个元素作为初始值,并跳过第一次循环。如果数组为空且未提供初始值,会直接报错Reduce of empty array with no initial value。 - 必须返回累计器 :回调函数最后必须
return accumulator(或更新后的累计值),否则下一次循环拿到的就是undefined。
-
- 优点 :功能极强,可以做求和、扁平化数组、分组、甚至实现
map和filter的组合功能。
- 优点 :功能极强,可以做求和、扁平化数组、分组、甚至实现
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(可选):原数组。- 返回值 :
true或false。
3. 适用业务场景
some(相当于逻辑或||):判断数组中是否"存在"满足条件的元素。例如:判断用户列表里是否有管理员权限(只要有一个就返回 true)。every(相当于逻辑与&&):判断数组是否"全员满足"条件。例如:提交表单前校验所有必填项是否都填写完毕。- 适用场景:表单全量校验、权限校验(判断用户是否拥有某个权限标识)。
4. 注意事项
- 短路特性 :
some找到第一个符合条件的就会停止遍历并返回true;every找到第一个不符合条件的就会停止遍历并返回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遍历数组! - 缺点 :
- 遍历的是键名(字符串形式的索引),不是值。
- 会遍历出数组对象自定义的可枚举属性。
- 遍历顺序不保证(某些旧引擎下)。
javascript
const arr = ['a', 'b', 'c'];
arr.customProp = 'hello'; // 给数组加了自定义属性
for (const key in arr) {
console.log(key); // '0', '1', '2', 'customProp' ❌ 遍历出了索引和自定义属性!
}
总结:如何选择?
面对这么多方法,记住下面这张决策树,保证你不会选错:
- 我想把数组变成另一种形态的新数组 ? ➡️
map() - 我想筛选出符合条件的新数组 ? ➡️
filter() - 我想把数组合并成一个值 (求和、转对象)? ➡️
reduce() - 我只想知道数组里有没有某项 满足条件? ➡️
some()/every() - 我想对每一项执行副作用(打印、修改外部变量),且没有异步 ? ➡️
forEach() - 循环里有
async/await,或者需要break跳出 ? ➡️for...of - 需要极其复杂的索引控制或追求极致性能? ➡️ 传统
for循环