循环是 JavaScript 中处理重复逻辑的核心语法,也是前端开发中最常使用的基础能力之一。从简单的数组遍历到复杂的异步任务处理,不同场景下选择合适的循环方式,既能提升代码可读性,也能优化执行效率。本文将从基础到进阶,全面拆解 JS 循环的类型、用法、场景选型和性能优化,帮你彻底掌握这一核心知识点。
一、循环的核心价值
在编程中,当需要对一组数据执行相同操作(如遍历数组、计算总和、筛选数据),或重复执行某段逻辑直到满足条件时,循环可以避免重复编写冗余代码,实现:
- 批量处理数据(如数组遍历、对象属性操作);
- 条件性重复执行(如累加计算、轮询接口);
- 自动化逻辑(如生成 DOM 列表、数据格式化)。
二、JS 循环的分类与基础用法
JS 中的循环主要分为两大阵营:传统基础循环 (手动控制迭代)和现代迭代器循环(语义化遍历),下面逐一拆解。
2.1 传统基础循环:手动掌控每一步
这类循环需要开发者手动定义初始化、循环条件和迭代规则,可控性最强,适合已知循环次数或需要精细控制的场景。
(1)for 循环:结构化最强的循环
语法:
javascript
运行
for (初始化表达式; 条件表达式; 更新表达式) {
// 循环体:条件为true时执行
}
核心场景:遍历数组(性能最优)
javascript
运行
const arr = [10, 20, 30, 40];
// 正向遍历
for (let i = 0; i < arr.length; i++) {
console.log(`索引${i}的值:${arr[i]}`);
}
// 反向遍历(性能略优,减少length查询次数)
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]);
}
// 优化:缓存length(大数据遍历推荐)
for (let i = 0, len = arr.length; i < len; i++) {
// 避免每次循环都查询arr.length
}
关键特性:
- 支持
break(终止循环)、continue(跳过当前轮次); - 可手动控制索引,适合需要操作索引的场景(如数组切片、替换元素);
- 大数据遍历性能最优,无额外语法开销。
(2)while 循环:未知次数的循环
先判断条件,再执行循环体,适合循环次数不确定的场景。
语法与示例:累加至和大于 100
javascript
运行
let sum = 0;
let num = 1;
// 条件满足时持续执行
while (sum <= 100) {
sum += num;
num++;
}
console.log(sum); // 105(1+2+...+14)
(3)do...while 循环:至少执行一次的循环
与while的核心区别是先执行循环体,再判断条件,保证循环至少执行一次。
示例:用户输入验证
javascript
运行
let input;
do {
input = prompt('请输入数字:');
} while (isNaN(Number(input))); // 输入非数字则重复提示
console.log('输入的数字是:', input);
2.2 现代迭代器循环:语义化遍历
ES5 + 新增的迭代器循环,无需手动控制索引,语法更简洁,语义化更强,是日常开发的首选。
(1)for...in:遍历对象属性
专门用于遍历对象的可枚举属性(包括原型链上的属性),也可遍历数组,但不推荐(索引为字符串类型)。
核心用法:安全遍历对象
javascript
运行
const user = { name: '张三', age: 20, gender: '男' };
for (const key in user) {
// 过滤原型链属性(必须!避免遍历继承的属性)
if (user.hasOwnProperty(key)) {
console.log(`${key}: ${user[key]}`);
}
}
// 输出:
// name: 张三
// age: 20
// gender: 男
避坑提醒:
- 遍历数组时,
key是字符串(如 "0"),可能导致+key等操作出错; - 遍历顺序不固定,不适合依赖顺序的场景。
(2)for...of:遍历可迭代对象
ES6 新增的for...of是数组遍历的首选,可遍历所有可迭代对象(数组、字符串、Set、Map、Generator 等),兼顾语义化和可控性。
核心用法:
javascript
运行
// 遍历数组
const arr = [1, 2, 3];
for (const item of arr) {
if (item === 2) break; // 支持break/continue
console.log(item); // 1
}
// 遍历字符串
const str = 'hello';
for (const char of str) {
console.log(char); // h, e, l, l, o
}
// 遍历Map(键值对)
const map = new Map([['name', '李四'], ['age', 25]]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// 结合entries获取数组索引+值
for (const [index, item] of arr.entries()) {
console.log(`索引${index}:${item}`);
}
核心优势:
- 支持
break/continue/return中断循环; - 直接获取元素值,无需操作索引;
- 不遍历原型链,无需额外过滤。
2.3 数组专用迭代方法:函数式编程首选
ES5 为数组提供了一系列专用迭代方法,语义化极强,适合函数式编程,无需关心迭代细节,专注业务逻辑。
| 方法 | 核心作用 | 返回值 | 是否修改原数组 |
|---|---|---|---|
forEach |
遍历数组,执行回调 | 无(undefined) | 否 |
map |
遍历数组,返回处理后的新数组 | 新数组 | 否 |
filter |
筛选符合条件的元素,返回新数组 | 新数组 | 否 |
some |
判断是否至少一个元素满足条件 | 布尔值 | 否 |
every |
判断是否所有元素满足条件 | 布尔值 | 否 |
find |
查找第一个满足条件的元素 | 元素 /undefined | 否 |
reduce |
聚合数组,生成单个值(累加 / 拼接) | 任意类型(自定义) | 否 |
实战示例:
javascript
运行
const nums = [1, 2, 3, 4, 5];
// 1. forEach:简单遍历
nums.forEach((item, index) => {
console.log(`第${index+1}个元素:${item}`);
});
// 2. map:数据转换(如所有数乘2)
const doubleNums = nums.map(item => item * 2);
console.log(doubleNums); // [2, 4, 6, 8, 10]
// 3. filter:筛选偶数
const evenNums = nums.filter(item => item % 2 === 0);
console.log(evenNums); // [2, 4]
// 4. reduce:数组求和(初始值为0)
const sum = nums.reduce((total, current) => total + current, 0);
console.log(sum); // 15
// 5. some:判断是否有大于3的数
const hasBigNum = nums.some(item => item > 3);
console.log(hasBigNum); // true
注意 :forEach/map等方法无法用 break/continue 中断 ,若需中断循环,建议改用for...of或普通for循环。
三、循环的中断与跳过
不同循环的中断方式不同,整理如下:
| 关键字 | 作用 | 适用循环类型 |
|---|---|---|
break |
终止整个循环 | for/while/do...while/for...of |
continue |
跳过当前轮次,进入下一轮 | for/while/do...while/for...of |
return |
终止函数(含内部循环) | 所有循环(函数内) |
示例:中断循环
javascript
运行
// break终止for...of循环
const arr = [1, 2, 3, 4];
for (const item of arr) {
if (item === 3) break;
console.log(item); // 1, 2
}
// continue跳过当前轮次
for (let i = 0; i < 5; i++) {
if (i === 2) continue;
console.log(i); // 0, 1, 3, 4
}
四、循环选型指南:不同场景怎么选?
| 场景 | 推荐循环方式 | 原因 |
|---|---|---|
| 遍历数组(需中断 / 高性能) | 普通 for 循环 /for...of | 可控性强、性能优 |
| 遍历数组(函数式 / 无中断) | forEach/map/filter/reduce | 语义化强、代码简洁 |
| 遍历对象属性 | for...in + hasOwnProperty | 专门遍历对象,过滤原型链属性 |
| 遍历 Set/Map | for...of | 原生支持可迭代对象 |
| 未知循环次数(条件循环) | while/do...while | 无需提前定义次数 |
| 异步循环(如接口请求) | for...of + async/await | 支持异步中断,避免回调地狱 |
异步循环示例(for...of + async/await)
javascript
运行
// 模拟异步请求
const fetchData = (id) => {
return new Promise(resolve => {
setTimeout(() => resolve(`数据${id}`), 1000);
});
};
// 异步遍历数组,按顺序请求
const asyncLoop = async () => {
const ids = [1, 2, 3];
for (const id of ids) {
const data = await fetchData(id);
console.log(data); // 依次输出:数据1、数据2、数据3
}
};
asyncLoop();
五、循环性能优化技巧
-
缓存数组长度 :遍历大数据数组时,将
arr.length缓存到变量,避免每次循环查询(普通 for 循环适用);javascript
运行
// 优化前 for (let i = 0; i < arr.length; i++) {} // 优化后 for (let i = 0, len = arr.length; i < len; i++) {} -
减少循环内操作:避免在循环体中定义函数、创建对象等耗时操作,尽量移到循环外;
-
优先使用原生方法 :
map/filter等原生方法由引擎优化,性能优于手动遍历; -
避免死循环 :确保循环条件最终会变为
false(如迭代变量要递增 / 递减); -
大数据遍历用普通 for:普通 for 循环无额外语法开销,性能 > for...of > forEach。
六、常见坑点避坑
-
for...in 遍历数组 :索引是字符串类型,易导致
arr[key + 1]等操作出错,坚决避免; -
forEach 无法中断:若需中断,不要用 forEach,改用 for...of;
-
var 声明迭代变量 :for 循环中用 var 声明 i 会导致变量提升,建议用 let;
javascript
运行
// 错误示例:var声明导致所有回调共用一个i for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 输出3,3,3 } // 正确示例:let声明,每个循环独立i for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 输出0,1,2 } -
循环中修改原数组 :遍历数组时直接修改元素(如
arr[i] = xxx)可能导致遍历异常,建议先拷贝数组。
总结
- JS 循环分为传统基础循环(for/while/do...while)和现代迭代器循环(for...in/for...of/ 数组方法),核心是根据场景选择合适的类型;
- 遍历数组优先用 for...of(语义化)或普通 for(高性能),遍历对象用 for...in+hasOwnProperty;
- 数组专用方法(map/filter/reduce)适合函数式编程,但无法中断循环;异步循环优先用 for...of+async/await。
掌握循环的核心逻辑和选型技巧,能让你的代码既简洁又高效。如果需要针对 "嵌套循环优化""无限循环排查" 等进阶场景深入讲解,可以留言补充。
👉 **觉得有用的点点关注谢谢~**