目录
一、 传统 for 循环:一切的基石
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
二、 forEach():朴实无华的执行者
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
三、 for...of:现代异步遍历之王 (ES6)
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
四、 map():数据映射器
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
五、 filter():数据过滤器
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
六、 reduce():全能聚合器
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
七、 some() 和 every():布尔值质检员
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项
八、 for...in:请勿用于数组遍历
1. 语法
2. 参数介绍
3. 适用业务场景
4. 注意事项(针对数组)
总结:如何选择?
在前端开发中,我们每天都在和数组打交道。从渲染列表数据、处理接口返回的 JSON,到复杂的表单校验,几乎 80% 的业务逻辑都离不开数组遍历。
JavaScript 为我们提供了极其丰富的数组遍历方法。
一、 传统for循环:一切的基石
最原始、最硬核的遍历方式。通过控制索引来访问数组元素。
1. 语法
for (let i = 0; i < arr.length; i++) { // 执行代码 arr[i] }2. 参数介绍
i:循环控制变量(索引)。arr.length:数组的长度,作为循环的边界条件。
3. 适用业务场景
- 复杂的索引计算:比如需要跳跃式遍历(
i += 2),或者需要倒序遍历数组。 - 性能极致要求:在处理超大型数组(如十万级以上数据)时,
for循环的执行速度通常是最快的。
4. 注意事项
- 易引发差一错误:需要手动维护边界条件(
<还是<=极易写错),导致越界。 - 作用域问题:早期使用
var声明i会导致变量泄露到全局,现代开发必须使用let声明。 - 优点:极其灵活,可以通过
break跳出循环,通过continue跳过本次循环;可以倒序遍历。 - 缺点:语法较为繁琐,需要手动维护索引,容易发生越界错误(差一错误)。
const arr = ['Vue', 'React', 'Angular']; for (let i = 0; i < arr.length; i++) { console.log(i, arr[i]); }二、forEach():朴实无华的执行者
数组原生的遍历方法,它接收一个回调函数,对数组中的每个元素执行一次。
1. 语法
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。
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. 语法
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会等待上一个异步操作完成再进行下一次循环。
- 优点:
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. 语法
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就是浪费内存。
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. 语法
let newArray = array.filter(function(currentValue, index, array), thisArg)2. 参数介绍
currentValue:当前处理的元素。index(可选):当前元素的索引。array(可选):原数组。- 返回值:一个通过测试的新数组。如果没有通过测试的元素,返回空数组
[]。
3. 适用业务场景
- 搜索过滤:前端实现模糊搜索,过滤出包含关键字的数据。
- 条件筛选:如从订单列表中筛选出“已付款”状态的订单。
4. 注意事项
- 返回布尔值:回调函数内部必须返回一个条件表达式(真值或假值),如果无脑
return true则相当于深拷贝。 - 空数组处理:
filter不会对空数组进行检测。
- 优点:语义清晰,专门用于筛选数据,保持原数组不变。
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. 语法
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的组合功能。
- 优点:功能极强,可以做求和、扁平化数组、分组、甚至实现
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. 语法
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(数学上的逻辑,因为不存在不满足条件的项)。
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. 语法
for (let key in object) { // 执行代码 }2. 参数介绍
key:对象属性的键名(在数组中是字符串形式的索引 “0”, “1”)。object:被遍历的对象。
3. 适用业务场景
- 仅用于遍历普通对象的键值对。
4. 注意事项(针对数组)
- 会遍历自定义属性:如果你给数组对象加了自定义属性
arr.name = 'test',for...in会把它也遍历出来。 - 顺序不可靠:在某些旧引擎下,遍历顺序可能不按照数组的物理顺序。
- 性能差:因为它需要去查找原型链上的可枚举属性。
- 结论:永远不要用
for...in遍历数组! - 缺点:
- 遍历的是键名(字符串形式的索引),不是值。
- 会遍历出数组对象自定义的可枚举属性。
- 遍历顺序不保证(某些旧引擎下)。
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循环