循环是 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。
掌握循环的核心逻辑和选型技巧,能让你的代码既简洁又高效。如果需要针对 “嵌套循环优化”“无限循环排查” 等进阶场景深入讲解,可以留言补充。
👉 **觉得有用的点点关注谢谢~**