JS For 循环详解
学习 for/in、for/of 的区别 · 难度:入门 · +10XP
JavaScript For 循环完全指南
循环是编程中最基本的控制结构之一,用于重复执行代码块。JavaScript 提供了多种循环方式:经典的 for 循环、while 和 do...while 循环,以及 ES6 引入的 for...of(遍历可迭代对象)和 for...in(遍历对象属性)。选择合适的循环方式能让代码更简洁、更少出错、更易维护。
一、经典 for 循环 —— 精确控制索引
最基础也最灵活的循环形式,由三个部分(初始化、条件判断、每次迭代后执行)组成。适用于明确知道循环次数或需要精确控制索引值的场景。三部分都可以省略,但分号不能省。
// 语法结构:for (初始化; 条件; 每次迭代后执行)
for (let i = 0; i < 5; i++) {
console.log(第 ${i + 1} 次循环);
}
// 输出: 第1次 ~ 第5次
// 遍历数组(正向)
let fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];
for (let i = 0; i < fruits.length; i++) {
console.log(水果${i + 1}: ${fruits[i]});
}
// 倒序遍历数组
for (let i = fruits.length - 1; i >= 0; i--) {
console.log(倒序: ${fruits[i]});
}
// 步长不为1的循环(只打印偶数索引)
for (let i = 0; i < fruits.length; i += 2) {
console.log(fruits[i]); // 苹果、橙子、西瓜
}
// 嵌套循环 —— 经典九九乘法表
for (let i = 1; i <= 9; i++) {
let row = '';
for (let j = 1; j <= i; j++) {
row += ${j}×${i}=${i * j} ;
}
console.log(row);
}
二、while 和 do...while —— 条件驱动的循环
当不确定循环次数、只知循环终止条件时,while 系列循环更合适。while 先判断条件再执行(可能一次都不执行),do...while 先执行一次再判断条件(至少执行一次)。
// while —— 先判断后执行(可能零次)
let count = 0;
while (count < 3) {
console.log(计数: ${count});
count++;
}
// do...while —— 先执行后判断(至少一次)
let num;
let attempts = 0;
do {
num = Math.floor(Math.random() * 10);
attempts++;
console.log(第${attempts}次:抽到了 ${num});
} while (num !== 7);
console.log(恭喜!经过${attempts}次终于抽到了幸运数字7!);
// while 用于处理未知长度的数据
function countdown(n) {
while (n > 0) {
console.log(n);
n--;
}
console.log('发射!');
}
countdown(5); // 5, 4, 3, 2, 1, 发射!
// 危险:死循环!(不要运行这段代码)
// while (true) { console.log('永远循环'); }
三、for...of —— 遍历可迭代对象(强烈推荐)
ES6 引入的 for...of 是遍历数组、字符串、Set、Map、NodeList 等可迭代对象的最佳选择。语法简洁,不需要手动管理索引,也不像 for...in 那样会遍历到原型链上的属性。
// 遍历数组(最常用)
let colors = ['红', '橙', '黄', '绿', '青', '蓝', '紫'];
for (let color of colors) {
console.log(color);
}
// 遍历字符串(逐字符)
for (let char of 'JavaScript') {
console.log(char);
}
// 输出: J, a, v, a, S, c, r, i, p, t
// 遍历 Set(自动去重的集合)
let unique = new Set([1, 2, 2, 3, 3, 3, 4]);
for (let val of unique) {
console.log(val); // 1, 2, 3, 4
}
// 遍历 Map(键值对),用解构赋值同时获取键和值
let map = new Map([
['name', 'Alice'],
['age', 25],
['city', '北京']
]);
for (let [key, value] of map) {
console.log(${key}: ${value});
}
// 如果你需要索引,用 entries()
for (let [index, color] of colors.entries()) {
console.log(第${index}个颜色: ${color});
}
四、for...in —— 遍历对象属性(谨慎使用)
for...in 用于遍历对象的可枚举属性键名。注意它会沿原型链向上查找,所以通常需要配合 hasOwnProperty() 过滤。对于数组,不推荐使用 for...in(它遍历的是键名字符串而非值,还会包含自定义属性)。
let person = { name: '小明', age: 20, city: '南宁', hobby: '编程' };
// for...in 遍历对象
for (let key in person) {
console.log(${key} = ${person[key]});
}
// 安全遍历 —— 过滤掉原型链上的属性
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(自己的属性: ${key});
}
}
// 更推荐的方式:Object.keys() + for...of
for (let key of Object.keys(person)) {
console.log(${key}: ${person[key]});
}
// 同时获取键和值:Object.entries()
for (let [key, value] of Object.entries(person)) {
console.log(${key} => ${value});
}
// 错误的数组用法(不要这样用!)
let arr = ['a', 'b', 'c'];
arr.customProp = 'hello';
for (let key in arr) {
console.log(key); // "0", "1", "2", "customProp" —— 多出了customProp!
}
// 正确做法:用 for...of 或 forEach 遍历数组
五、循环控制关键字:break 与 continue
| 关键字 | 作用 | 影响范围 | 典型场景 |
|---|---|---|---|
| break | 立即终止整个循环 | 跳出当前循环体 | 找到目标后停止搜索 |
| continue | 跳过本次迭代剩余代码 | 进入下一次迭代 | 跳过不符合条件的元素 |
// break —— 找到即停止
let numbers = [4, 8, 12, 7, 15, 3, 19];
for (let n of numbers) {
if (n === 7) {
console.log(找到了7!索引: ${numbers.indexOf(7)});
break; // 找到后立即停止,不再检查后面的元素
}
console.log(检查了: ${n});
}
// 输出: 检查了:4, 检查了:8, 检查了:12, 找到了7!...
// continue —— 跳过特定元素
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) continue; // 跳过所有偶数
console.log(i); // 只打印奇数: 1, 3, 5, 7, 9
}
// 带标签的 break —— 跳出嵌套循环
outerLoop:
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
if (i === 2 && j === 2) {
break outerLoop; // 直接跳出外层循环
}
console.log(i=${i}, j=${j});
}
}
六、循环方式全面对比
| 循环类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| for (;;) | 需要精确控制索引、步长 | 最灵活,可完全控制 | 语法稍繁琐 |
| while | 不确定循环次数 | 逻辑清晰,适合条件等待 | 容易写出死循环 |
| do...while | 至少执行一次 | 保证首次执行 | 使用场景较少 |
| for...of | 遍历可迭代对象(数组、Set、Map) | 语法简洁,语义清晰 | 无法直接获取索引 |
| for...in | 遍历对象属性 | 直接获取键名 | 遍历原型链属性,需过滤 |
| forEach() | 函数式遍历数组 | 链式调用,结合箭头函数很简洁 | 无法 break/continue |
- 九九乘法表:用双重 for 循环打印完整的九九乘法表,要求格式对齐整齐。格式参考:1x1=1, 1x2=2 2x2=4, ... 直到 9x9=81。使用 padStart 方法确保每个算式等宽显示。
- 数组元素查找:给定数组 [12, 45, 23, 67, 34, 89, 56],用 for 循环找到第一个大于 50 的元素及其索引,找到后使用 break 终止循环。再用 for...of 和普通的 for 循环各实现一遍。
- FizzBuzz 经典面试题:用 for 循环输出 1 到 100。数字是 3 的倍数输出 "Fizz",是 5 的倍数输出 "Buzz",同时是 3 和 5 的倍数输出 "FizzBuzz",其他情况输出数字本身。
- 对象遍历方式对比:创建至少包含 5 个属性的 student 对象(姓名、年龄、班级、成绩、爱好)。分别用 for...in、Object.keys()+for...of、Object.entries()+for...of 三种方式遍历,对比输出结果的差异和各自的适用场景。
- 猜数字游戏:用 while 循环实现完整的猜数字游戏。随机生成 1 到 100 之间的整数,每次用 prompt 接收用户猜测,反馈"大了"或"小了",猜对时显示祝贺信息和猜测次数。额外处理:输入非数字的容错,输入 q 退出的功能。