Appearance
11|JavaScript 循环与迭代
重复执行同一段逻辑是编程中最常见的模式:遍历服务器列表检查状态、批量处理日志条目、渲染表格的每一行。JavaScript 提供多种循环结构,每种适合不同的场景。
一、for 循环
标准 for 循环由初始化、条件判断、迭代语句三部分组成:
js
for (let i = 0; i < 5; i++) {
console.log(`当前索引: ${i}`);
}
// 输出 0, 1, 2, 3, 4执行顺序:
let i = 0—— 初始化,只执行一次i < 5—— 条件判断,为true时进入循环体- 执行循环体代码
i++—— 迭代语句- 回到步骤 2
遍历数组的典型写法:
js
const services = ["nginx", "redis", "mysql"];
for (let i = 0; i < services.length; i++) {
console.log(`${i}: ${services[i]}`);
}二、while 循环
不知道循环次数时,用 while:
js
let retry = 0;
let success = false;
while (!success && retry < 3) {
success = tryConnect();
retry++;
}条件为 true 时持续循环。如果条件一开始就为 false,循环体一次都不会执行。
do...while
js
let password;
do {
password = prompt("请输入密码");
} while (password !== "admin");do...while 至少执行一次循环体,然后检查条件。适合需要先执行再判断的场景。
三、for...of
遍历可迭代对象(数组、字符串、Map、Set)的元素值:
js
const services = ["nginx", "redis", "mysql"];
for (const service of services) {
console.log(service);
}
// nginx
// redis
// mysql比传统 for 循环简洁,且不需要手动管理索引。但不能直接获取索引,需要时用 entries():
js
for (const [index, service] of services.entries()) {
console.log(`${index}: ${service}`);
}四、for...in
遍历对象的所有可枚举属性名(键):
js
const server = { hostname: "web-01", ip: "192.168.1.10" };
for (const key in server) {
console.log(`${key}: ${server[key]}`);
}
// hostname: web-01
// ip: 192.168.1.10不要在数组上用 for...in。数组也是对象,for...in 会遍历所有可枚举属性(包括手动添加的属性和原型链上的属性),且遍历顺序不保证:
js
const arr = ["a", "b"];
arr.name = "test"; // 给数组添加非数字属性
for (const key in arr) {
console.log(key); // "0", "1", "name" —— 不期望的结果
}五、数组迭代方法
数组提供了一系列声明式的迭代方法,比手写循环更简洁、更安全。
forEach
js
const services = ["nginx", "redis", "mysql"];
services.forEach((service, index) => {
console.log(`${index}: ${service}`);
});对每个元素执行回调函数。没有返回值,不能用 break 中断。
map
对每个元素做转换,返回新数组:
js
const ports = [80, 443, 3306];
const labels = ports.map((port) => `端口: ${port}`);
// ["端口: 80", "端口: 443", "端口: 3306"]原数组不变,map 返回新数组。适合数据转换场景。
filter
按条件筛选元素,返回满足条件的新数组:
js
const services = [
{ name: "nginx", status: "running" },
{ name: "redis", status: "stopped" },
{ name: "mysql", status: "running" },
];
const running = services.filter((s) => s.status === "running");
// [{ name: "nginx", status: "running" }, { name: "mysql", status: "running" }]find
返回第一个满足条件的元素:
js
const mysql = services.find((s) => s.name === "mysql");
// { name: "mysql", status: "running" }找不到时返回 undefined。
findIndex
返回第一个满足条件的元素的索引:
js
const index = services.findIndex((s) => s.name === "redis");
// 1some / every
js
// 是否有至少一个元素满足条件
const hasStopped = services.some((s) => s.status === "stopped"); // true
// 是否所有元素都满足条件
const allRunning = services.every((s) => s.status === "running"); // falsereduce
把数组归约为单个值:
js
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 0 是初始值,acc 是累加器
// 过程: 0+1=1, 1+2=3, 3+3=6, 6+4=10reduce 很强大但可读性较差。简单的求和、求最大/最小值可以用它,复杂逻辑建议拆成多步 map/filter。
六、循环控制
break
立即终止整个循环:
js
for (const service of services) {
if (service.status === "stopped") {
console.log(`发现停止的服务: ${service.name}`);
break; // 找到第一个就停止
}
}continue
跳过当前迭代,进入下一次:
js
for (const service of services) {
if (service.status === "stopped") {
continue; // 跳过停止的服务,不执行后续逻辑
}
console.log(`${service.name} 正在运行`);
}七、常见错误
数组长度在循环中变化
js
// 错误:删除元素导致索引错位
for (let i = 0; i < arr.length; i++) {
if (arr[i] < 0) {
arr.splice(i, 1); // 删除后,后续元素前移,会漏掉一个
}
}
// 正确:倒序遍历,或先用 filter 生成新数组
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] < 0) {
arr.splice(i, 1);
}
}在 forEach 中用 return 代替 continue
js
// 错误:return 会退出整个函数,不只是当前迭代
services.forEach((s) => {
if (s.status === "stopped") {
return; // 只跳出当前回调,效果类似 continue
}
});
// 但如果在另一个函数内部使用,return 的行为容易混淆
// 建议:复杂逻辑改用 for...of
for (const s of services) {
if (s.status === "stopped") continue;
// ...
}在 forEach 中用 await
js
// 错误:forEach 不会等待异步操作完成
urls.forEach(async (url) => {
const data = await fetch(url); // 不会按顺序等待
});
// 正确:用 for...of
for (const url of urls) {
const data = await fetch(url); // 按顺序等待
}修改正在遍历的数组
js
const arr = [1, 2, 3];
// 错误:在 map 中修改原数组
arr.map((item, index) => {
arr[index] = item * 2; // 副作用,map 的返回值被忽略
});
// 正确:用 map 的返回值
const doubled = arr.map((item) => item * 2);