Introduction

因为C/Java/Python/JS的for, while, do while循环可以说是一幕一样的。所以如果有相关基础就可以直接跳过了。所以我这里的讲授方式是面向beginner的。

但在这一章我还额外讲了一个javascripts特有for in(类似python的for key in dict)和for of(类似python的(for value in list))。for in不太常用,但是for of用的还是不算少的因此值得跳到相应位置了解下。

什么是循环

循环是编程中的基本控制结构,它允许我们重复执行一段代码直到满足特定条件。 就像告诉某人”向东走5步”一样,循环让计算机能够自动重复执行相同的操作。

// 简单的循环示例
for (let step = 0; step < 5; step++) {
console.log("向东走一步"); // 执行5次
}

JavaScript 提供了多种循环语句,每种都适用于不同的场景。

for 循环

for 循环是最常用的循环类型,适用于已知循环次数的情况。

基本语法

for (初始化; 条件; 更新) {
// 执行的代码
}

执行顺序

  1. 执行初始化表达式(通常是声明计数器变量)
  2. 检查条件,如果为 true 则执行循环体
  3. 执行循环体中的代码
  4. 执行更新表达式(通常是递增计数器)
  5. 返回步骤2,继续检查条件

实际示例

// 计算1到10的和
let sum = 0;
for (let i = 1; i <= 10; i++) {
sum += i;
console.log(`当前i=${i}, 累计sum=${sum}`);
}
console.log(`最终结果: ${sum}`); // 55
// 遍历数组
const fruits = ["apple", "banana", "orange"];
for (let i = 0; i < fruits.length; i++) {
console.log(`第${i + 1}个水果是: ${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 循环

while 循环在条件为真时持续执行,适用于不确定循环次数的情况。

基本语法

while (条件) {
// 执行的代码
}

实际示例

// 生成随机数直到得到指定值
let randomNum;
let attempts = 0;
while (randomNum !== 7) {
randomNum = Math.floor(Math.random() * 10) + 1; // 1-10的随机数
attempts++;
console.log(`第${attempts}次尝试,得到数字: ${randomNum}`);
}
console.log(`总共尝试了${attempts}次`);
// 用户输入验证(模拟场景)
let userInput = "";
const validInputs = ["yes", "no"];
while (!validInputs.includes(userInput.toLowerCase())) {
// 在实际应用中,这里会是真实的用户输入
userInput = Math.random() > 0.7 ? "yes" : "maybe"; // 模拟输入
console.log(`用户输入: ${userInput}`);
if (!validInputs.includes(userInput.toLowerCase())) {
console.log("请输入 'yes' 或 'no'");
}
}

do…while 循环

do...while 循环至少执行一次循环体,然后检查条件。

基本语法

do {
// 执行的代码
} while (条件);

实际示例

// 至少询问用户一次
let userWantsToContinue;
do {
console.log("执行一些操作...");
// 模拟用户选择
userWantsToContinue = Math.random() > 0.6;
console.log(`用户是否想继续: ${userWantsToContinue}`);
} while (userWantsToContinue);
// 菜单系统
let choice;
do {
console.log("=== 主菜单 ===");
console.log("1. 选项1");
console.log("2. 选项2");
console.log("0. 退出");
// 模拟用户选择
choice = Math.floor(Math.random() * 3); // 0, 1, 或 2
console.log(`用户选择: ${choice}`);
switch (choice) {
case 1:
console.log("执行选项1");
break;
case 2:
console.log("执行选项2");
break;
case 0:
console.log("再见!");
break;
}
} while (choice !== 0);

for…in 循环

for...in 循环用于遍历对象的可枚举属性。

基本语法

for (let key in object) {
// 执行的代码
}

实际示例

const person = {
name: "Kayn",
age: 17,
city: "Shanghai",
hobby: "coding",
};
// 遍历对象属性
for (let key in person) {
console.log(`${key}: ${person[key]}`);
}
// 统计对象属性数量
let propertyCount = 0;
for (let key in person) {
propertyCount++;
}
console.log(`对象共有 ${propertyCount} 个属性`);
// 注意:for...in 也能遍历数组,但不推荐
const colors = ["red", "green", "blue"];
for (let index in colors) {
console.log(`索引${index}: ${colors[index]}`); // index 是字符串类型
}

PS:不常用的原因

  • 因为它会枚举对象原型链上的可枚举属性,而且遍历数组时拿到的是下标(字符串形式),容易出 bug。
  • 一般用来遍历“纯对象”的属性(而不是数组),但现在更多人倾向于用以下语句来
Object.keys(obj).forEach(...)
Object.entries(obj).forEach(([k,v]) => ...)

来代替。

👉 所以 for..in 出现频率 越来越低。

for…of 循环

for...of 循环用于遍历可迭代对象(数组、字符串、Map、Set等)的值。

基本语法

for (let value of iterable) {
// 执行的代码
}

实际示例

// 遍历数组
const numbers = [10, 20, 30, 40, 50];
for (let num of numbers) {
console.log(`当前数字: ${num}`);
}
// 遍历字符串
const message = "Hello";
for (let char of message) {
console.log(`字符: ${char}`);
}
// 遍历 Set
const uniqueNumbers = new Set([1, 2, 3, 2, 1]);
for (let num of uniqueNumbers) {
console.log(`唯一数字: ${num}`); // 1, 2, 3
}
// 遍历 Map
const userMap = new Map([
["name", "Kayn"],
["age", 17],
["role", "student"],
]);
for (let [key, value] of userMap) {
console.log(`${key} -> ${value}`);
}
// 同时获取数组的索引和值
const fruits = ["apple", "banana", "orange"];
for (let [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}

循环控制语句

break 语句

break 用于立即跳出循环

// 找到第一个偶数就停止
const numbers = [1, 3, 5, 8, 9, 12];
for (let num of numbers) {
if (num % 2 === 0) {
console.log(`找到第一个偶数: ${num}`);
break; // 跳出循环
}
console.log(`检查数字: ${num}`);
}
// 在嵌套循环中使用 break
outer: for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
if (i === 2 && j === 2) {
console.log("跳出外层循环");
break outer; // 跳出外层循环
}
console.log(`i=${i}, j=${j}`);
}
}

continue 语句

continue 用于跳过当前迭代,继续下一次迭代

// 跳过偶数,只打印奇数
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) {
continue; // 跳过偶数
}
console.log(`奇数: ${i}`);
}
// 处理数组中的有效数据
const data = [1, null, 3, undefined, 5, "", 7];
for (let item of data) {
if (!item) {
continue; // 跳过无效数据
}
console.log(`处理数据: ${item}`);
}

循环对比和选择

各种循环的适用场景

循环类型适用场景优点注意事项
for已知循环次数简洁、性能好适合数组遍历
while条件循环,次数不确定灵活小心死循环
do…while至少执行一次保证执行使用较少
for…in遍历对象属性简单直接会遍历原型链
for…of遍历可迭代对象的值现代、清晰不能直接获取索引

性能对比

const largeArray = Array.from({ length: 1000000 }, (_, i) => i);
// 性能测试函数
function timeTest(name, fn) {
const start = performance.now();
fn();
const end = performance.now();
console.log(`${name}: ${end - start}ms`);
}
// 传统 for 循环 - 通常最快
timeTest("传统 for 循环", () => {
let sum = 0;
for (let i = 0; i < largeArray.length; i++) {
sum += largeArray[i];
}
});
// for...of 循环 - 稍慢但更易读
timeTest("for...of 循环", () => {
let sum = 0;
for (let num of largeArray) {
sum += num;
}
});
// forEach 方法 - 最慢但功能强大
timeTest("forEach 方法", () => {
let sum = 0;
largeArray.forEach((num) => (sum += num));
});

实际应用场景

1. 数据处理

// 购物车总价计算
const cart = [
{ name: "笔记本", price: 1200, quantity: 1 },
{ name: "鼠标", price: 80, quantity: 2 },
{ name: "键盘", price: 200, quantity: 1 },
];
let total = 0;
for (let item of cart) {
const itemTotal = item.price * item.quantity;
console.log(`${item.name}: ${itemTotal}元`);
total += itemTotal;
}
console.log(`购物车总计: ${total}元`);

2. DOM 操作

// 批量处理页面元素(浏览器环境)
const buttons = document.querySelectorAll(".btn");
for (let button of buttons) {
button.addEventListener("click", function () {
console.log(`按钮被点击: ${this.textContent}`);
});
}
// 动态创建列表项
const todoList = ["学习JavaScript", "完成作业", "锻炼身体"];
const ul = document.createElement("ul");
for (let [index, todo] of todoList.entries()) {
const li = document.createElement("li");
li.textContent = `${index + 1}. ${todo}`;
ul.appendChild(li);
}

3. 算法实现

// 冒泡排序
function bubbleSort(arr) {
const result = [...arr]; // 复制数组
for (let i = 0; i < result.length - 1; i++) {
let swapped = false;
for (let j = 0; j < result.length - 1 - i; j++) {
if (result[j] > result[j + 1]) {
// 交换元素
[result[j], result[j + 1]] = [result[j + 1], result[j]];
swapped = true;
}
}
if (!swapped) break; // 优化:如果没有交换说明已排序
}
return result;
}
const numbers = [64, 34, 25, 12, 22, 11, 90];
console.log("排序前:", numbers);
console.log("排序后:", bubbleSort(numbers));

常见陷阱和最佳实践

1. 避免死循环

// ❌ 死循环示例
// let i = 0;
// while (i < 10) {
// console.log(i);
// // 忘记更新 i,导致死循环
// }
// ✅ 正确做法
let i = 0;
while (i < 10) {
console.log(i);
i++; // 记得更新计数器
}

2. for…in 的陷阱

const arr = [1, 2, 3];
arr.customProperty = "自定义属性";
// ❌ for...in 会遍历自定义属性
console.log("使用 for...in:");
for (let key in arr) {
console.log(key, arr[key]); // 会包括 customProperty
}
// ✅ for...of 只遍历数组元素
console.log("使用 for...of:");
for (let value of arr) {
console.log(value); // 只有 1, 2, 3
}

3. 循环中的异步操作

// ❌ 问题代码
console.log("错误的异步循环:");
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出三个 3
}, 100);
}
// ✅ 解决方案1:使用 let 声明
console.log("使用 let 解决:");
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0, 1, 2
}, 200);
}
// ✅ 解决方案2:使用闭包
console.log("使用闭包解决:");
for (var i = 0; i < 3; i++) {
((index) => {
setTimeout(() => {
console.log(index); // 输出 0, 1, 2
}, 300);
})(i);
}

4. 性能优化建议

const largeArray = Array.from({ length: 10000 }, (_, i) => i);
// ❌ 每次都计算 length
for (let i = 0; i < largeArray.length; i++) {
// 处理数据
}
// ✅ 缓存 length 值
const len = largeArray.length;
for (let i = 0; i < len; i++) {
// 处理数据
}
// ✅ 或者使用 for...of(更简洁)
for (let item of largeArray) {
// 处理数据
}

现代 JavaScript 中的循环

数组方法 vs 传统循环

const numbers = [1, 2, 3, 4, 5];
// 传统 for 循环
let doubled1 = [];
for (let i = 0; i < numbers.length; i++) {
doubled1.push(numbers[i] * 2);
}
// 现代方法:map
let doubled2 = numbers.map((n) => n * 2);
// 传统循环查找
let found1 = null;
for (let num of numbers) {
if (num > 3) {
found1 = num;
break;
}
}
// 现代方法:find
let found2 = numbers.find((n) => n > 3);
console.log("传统循环结果:", doubled1, found1);
console.log("现代方法结果:", doubled2, found2);

异步循环的现代处理

// 模拟异步操作
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function processItems() {
const items = [1, 2, 3, 4, 5];
// 顺序处理
console.log("顺序处理:");
for (let item of items) {
await delay(100);
console.log(`处理项目: ${item}`);
}
// 并行处理
console.log("并行处理:");
const promises = items.map(async (item) => {
await delay(100);
return `处理项目: ${item}`;
});
const results = await Promise.all(promises);
results.forEach((result) => console.log(result));
}
// processItems(); // 取消注释以运行

总结

循环是编程的基础工具,选择合适的循环类型能让代码更高效、更易读:

  • for 循环:最通用,性能最好
  • while/do…while:条件控制,灵活性强
  • for…in:对象属性遍历
  • for…of:现代的值遍历方式

记住要避免死循环、注意异步操作的陷阱,并且在现代 JavaScript 开发中考虑使用数组方法来替代传统循环,以获得更好的可读性和函数式编程的优势。