JavaScriptfor循环学习笔记
循环是编程中最核心的控制结构之一,用于重复执行一段代码,直到满足特定条件。JavaScript 提供了多种循环方式,每种都有其特定的使用场景。
1. 标准for循环
最经典、最灵活的循环结构,适用于已知循环次数或需要精细控制的场景。
1.1 语法结构
for(初始化表达式;条件表达式;更新表达式){// 循环体:要重复执行的代码}执行流程:
- 初始化:只执行一次(通常用于声明计数器)。
- 条件判断:如果为
true,执行循环体;如果为false,跳出循环。 - 执行循环体。
- 更新:执行更新表达式(通常用于计数器自增/自减)。
- 回到步骤 2。
1.2 基础示例
// 打印 0 到 4for(leti=0;i<5;i++){console.log(i);}// 输出:0, 1, 2, 3, 41.3 关键细节
letvsvar:- 使用
let声明循环变量:变量具有块级作用域,每次迭代都是独立的(解决闭包问题)。 - 使用
var声明:变量具有函数作用域,所有迭代共享同一个变量(在异步回调中容易出错)。
// ✅ 推荐 (let)for(leti=0;i<3;i++){setTimeout(()=>console.log(i),100);}// 输出:0, 1, 2 (正确)// ❌ 不推荐 (var)for(varj=0;j<3;j++){setTimeout(()=>console.log(j),100);}// 输出:3, 3, 3 (错误,循环结束时 j 为 3)- 使用
无限循环风险:如果条件永远为
true且没有break,会导致浏览器卡死。// 危险!// for (let i = 0; ; i++) { ... }
2.for...of循环 (ES6)
推荐用于遍历可迭代对象(数组、字符串、Map、Set 等)。它直接获取值,语法简洁。
2.1 语法
for(variableofiterable){// 循环体}2.2 示例
constfruits=["苹果","香蕉","橙子"];// 遍历数组值for(constfruitoffruits){console.log(fruit);}// 输出:苹果, 香蕉, 橙子// 遍历字符串for(constcharof"Hello"){console.log(char);}// 输出:H, e, l, l, o// 遍历 Map (获取 [key, value] 数组)constmap=newMap([["a",1],["b",2]]);for(const[key,value]ofmap){console.log(`${key}:${value}`);}2.3 特点
- 只读值:默认获取的是值,不是索引。如果需要索引,需配合
entries()或手动计数。 - 支持
break和continue。 - 不能直接遍历普通对象(对象不是可迭代对象,除非自定义 Symbol.iterator)。
3.for...in循环
专门用于遍历对象的可枚举属性(键名)。也可以用于数组,但不推荐(因为会遍历原型链上的属性且顺序不保证)。
3.1 语法
for(variableinobject){// 循环体}3.2 示例
constperson={name:"Alice",age:25,city:"Beijing"};// 遍历对象键for(constkeyinperson){console.log(`${key}:${person[key]}`);}// 输出:name: Alice, age: 25, city: Beijing// ❌ 不推荐用于数组 (会遍历原型链,顺序不确定)constarr=["a","b","c"];arr.customProp="test";// 自定义属性for(constindexinarr){console.log(index);// 输出:0, 1, 2, "customProp" (包含自定义属性!)}3.3 注意事项
- 遍历的是键名 (Key),不是值。
- 顺序不保证:虽然现代引擎通常按插入顺序遍历,但规范未强制。
- 包含原型链:如果对象原型上有可枚举属性,也会被遍历到。
constobj={a:1};Object.prototype.b=2;// 污染原型 (不推荐)for(constkeyinobj){console.log(key);// 输出:a, b}// 解决方案:使用 hasOwnProperty 过滤for(constkeyinobj){if(obj.hasOwnProperty(key)){console.log(key);// 只输出:a}}
4.while循环
当循环次数未知,仅依赖条件判断时使用。
4.1 语法
while(条件表达式){// 循环体// 必须包含更新条件的代码,否则死循环}4.2 示例
letcount=0;while(count<5){console.log(count);count++;// 必须手动更新}4.3 适用场景
- 等待异步操作完成。
- 读取流数据直到结束。
- 游戏主循环。
5.do...while循环
至少执行一次循环体,然后再判断条件。
5.1 语法
do{// 循环体}while(条件表达式);5.2 示例
letinput;do{input=prompt("请输入密码 (至少6位):");}while(input.length<6);console.log("密码有效");特点:即使条件一开始就为false,循环体也会执行一次。
6. 循环控制语句
6.1break
立即终止整个循环,跳出循环体。
for(leti=0;i<10;i++){if(i===5){break;// 当 i 为 5 时,直接跳出}console.log(i);// 输出 0, 1, 2, 3, 4}6.2continue
跳过当前迭代,直接进入下一次循环(执行更新表达式,然后判断条件)。
for(leti=0;i<5;i++){if(i===2){continue;// 跳过 i=2 的打印}console.log(i);// 输出:0, 1, 3, 4}6.3 标签 (Labels)
用于控制嵌套循环的跳出。
outerLoop:for(leti=0;i<3;i++){for(letj=0;j<3;j++){if(i===1&&j===1){breakouterLoop;// 跳出外层循环}console.log(`i=${i}, j=${j}`);}}// 输出:// i=0, j=0// i=0, j=1// i=0, j=2// i=1, j=0// (i=1, j=1 时直接跳出,不再执行后续)7. 循环性能与最佳实践
7.1 性能对比
- 标准
for:通常性能最好,尤其是遍历数组时。 for...of:性能略低于标准for,但代码更简洁,现代引擎优化得很好。for...in:性能最差,严禁用于数组遍历。forEach/map/filter:函数式方法,代码可读性好,但性能略低于for循环(因为函数调用开销),且无法使用break/continue。
7.2 选择指南
| 场景 | 推荐循环 | 理由 |
|---|---|---|
| 遍历数组 (需要索引) | for或for...of+entries() | 性能高,控制灵活 |
| 遍历数组 (只需值) | for...of | 语法简洁,语义清晰 |
| 遍历对象属性 | for...in(配合hasOwnProperty) | 唯一原生方式 |
| 遍历 Map/Set | for...of | 原生支持迭代器 |
| 未知循环次数 | while/do...while | 依赖条件判断 |
| 需要中断循环 | for/while/for...of | 支持break |
| 函数式编程风格 | forEach/map/filter | 链式调用,无副作用 |
7.3 常见陷阱
修改数组长度:在
for循环中修改数组长度(如push/pop)可能导致跳过元素或死循环。constarr=[1,2,3];for(leti=0;i<arr.length;i++){if(arr[i]===2){arr.push(4);// 修改长度}}// 可能导致无限循环或逻辑错误解决:缓存长度
const len = arr.length,或使用for...of。异步循环:在循环中使用
async/await时,for...of会等待每个迭代完成,而forEach不会。// ✅ 正确:等待每个请求完成for(consturlofurls){awaitfetch(url);}// ❌ 错误:所有请求同时发起,无法控制顺序urls.forEach(async(url)=>{awaitfetch(url);});for...in遍历数组:- 顺序不保证。
- 会遍历原型链属性。
- 索引是字符串类型。
- 永远不要用
for...in遍历数组!
8. 实战示例
示例 1:查找数组中的元素
functionfindIndex(arr,target){for(leti=0;i<arr.length;i++){if(arr[i]===target){returni;}}return-1;}示例 2:累加数组元素
constnumbers=[1,2,3,4,5];letsum=0;// 使用 for...offor(constnumofnumbers){sum+=num;}console.log(sum);// 15示例 3:对象属性过滤
constusers=[{name:"Alice",age:25,active:true},{name:"Bob",age:30,active:false},{name:"Charlie",age:22,active:true}];// 找出所有活跃用户constactiveUsers=[];for(constuserofusers){if(user.active){activeUsers.push(user);}}示例 4:嵌套循环 (打印乘法表)
for(leti=1;i<=9;i++){letrow="";for(letj=1;j<=i;j++){row+=`${j}x${i}=${i*j}\t`;}console.log(row);}9. 总结速查表
| 循环类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
for | 需要索引、精细控制、性能敏感 | 灵活、性能高 | 语法稍繁琐 |
for...of | 遍历数组、字符串、Map、Set | 语法简洁、语义清晰 | 无法直接获取索引 (需配合entries) |
for...in | 遍历对象键 | 唯一遍历对象方式 | 顺序不定、包含原型链、不可用于数组 |
while | 未知次数、条件驱动 | 灵活 | 容易忘记更新条件导致死循环 |
do...while | 至少执行一次 | 保证执行一次 | 语法稍特殊 |
forEach | 函数式风格、无中断需求 | 代码简洁、链式调用 | 无法break/continue、性能略低 |
核心建议:
- 遍历数组:优先用
for...of(只需值) 或for(需索引)。 - 遍历对象:用
for...in(配合hasOwnProperty) 或Object.keys()。 - 避免在循环中修改被遍历的数组长度。
- 异步操作使用
for...of+await。 - 始终使用
let声明循环变量。
掌握这些循环方式,你就能应对 JavaScript 中绝大多数重复性任务!