Skip to content

11|JavaScript 循环与迭代

重复执行同一段逻辑是编程中最常见的模式:遍历服务器列表检查状态、批量处理日志条目、渲染表格的每一行。JavaScript 提供多种循环结构,每种适合不同的场景。

一、for 循环

标准 for 循环由初始化、条件判断、迭代语句三部分组成:

js
for (let i = 0; i < 5; i++) {
  console.log(`当前索引: ${i}`);
}
// 输出 0, 1, 2, 3, 4

执行顺序:

  1. let i = 0 —— 初始化,只执行一次
  2. i < 5 —— 条件判断,为 true 时进入循环体
  3. 执行循环体代码
  4. i++ —— 迭代语句
  5. 回到步骤 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");
// 1

some / every

js
// 是否有至少一个元素满足条件
const hasStopped = services.some((s) => s.status === "stopped");   // true

// 是否所有元素都满足条件
const allRunning = services.every((s) => s.status === "running");   // false

reduce

把数组归约为单个值:

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=10

reduce 很强大但可读性较差。简单的求和、求最大/最小值可以用它,复杂逻辑建议拆成多步 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);