for

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

for 语句创建了一个循环,它由三个可选的表达式组成,这些表达式用括号括起来并用分号分隔,后面跟着一个在循环中执行的语句(通常是一个块语句)。

试一试

let str = "";

for (let i = 0; i < 9; i++) {
  str += i;
}

console.log(str);
// Expected output: "012345678"

语法

js
for (initialization; condition; afterthought)
  statement
initialization 可选

在循环开始前只评估一次的表达式(包括赋值表达式)或变量声明。通常用于初始化计数器变量。此表达式可以选择使用 varlet 关键字声明新变量。用 var 声明的变量不局限于循环,即它们与 for 循环在同一作用域中。用 let 声明的变量是该语句的局部变量。

此表达式的结果被丢弃。

condition 可选

在每次循环迭代之前评估的表达式。如果此表达式评估为 true,则执行 statement。如果表达式评估为 false,则执行退出循环并转到 for 结构后面的第一个语句。

此条件测试是可选的。如果省略,条件始终评估为 true。

afterthought 可选

在每次循环迭代结束时评估的表达式。这发生在下一次 condition 评估之前。通常用于更新或递增计数器变量。

statement

只要条件评估为 true,就会执行的语句。你可以使用块语句来执行多个语句。要在循环中不执行任何语句,请使用空语句;)。

描述

像其他循环语句一样,你可以在 statement 中使用控制流语句

  • break 会停止 statement 的执行,并跳转到循环之后的第一条语句。
  • continue 停止 statement 的执行,并重新评估 afterthought,然后是 condition

示例

使用 for 循环

以下 for 语句首先声明变量 i 并将其初始化为 0。它检查 i 是否小于 9,执行随后的两个语句,并在每次循环通过后将 i 递增 1。

js
for (let i = 0; i < 9; i++) {
  console.log(i);
  // more statements
}

初始化块语法

初始化块接受表达式和变量声明。但是,表达式不能使用未加括号的 in 运算符,因为这与 for...in 循环存在歧义。

js
for (let i = "start" in window ? window.start : 0; i < 9; i++) {
  console.log(i);
}
// SyntaxError: 'for-in' loop variable declaration may not have an initializer.
js
// Parenthesize the whole initializer
for (let i = ("start" in window ? window.start : 0); i < 9; i++) {
  console.log(i);
}

// Parenthesize the `in` expression
for (let i = ("start" in window) ? window.start : 0; i < 9; i++) {
  console.log(i);
}

可选的 for 表达式

for 循环头部中的所有三个表达式都是可选的。例如,不强制使用 initialization 块来初始化变量。

js
let i = 0;
for (; i < 9; i++) {
  console.log(i);
  // more statements
}

initialization 块一样,condition 部分也是可选的。如果你省略此表达式,则必须确保在循环体中中断循环,以免创建无限循环。

js
for (let i = 0; ; i++) {
  console.log(i);
  if (i > 3) break;
  // more statements
}

你也可以省略所有三个表达式。同样,请确保使用 break 语句来结束循环,并修改(增加)一个变量,以便中断语句的条件在某个时刻为 true。

js
let i = 0;

for (;;) {
  if (i > 3) break;
  console.log(i);
  i++;
}

然而,在你不完全使用所有三个表达式位置的情况下——特别是如果你没有用第一个表达式声明变量而是在上层作用域中修改某些东西——请考虑使用 while 循环,这样可以使意图更清晰。

js
let i = 0;

while (i <= 3) {
  console.log(i);
  i++;
}

初始化块中的词法声明

在初始化块中声明变量与在上层作用域中声明变量有重要的区别,尤其是在循环体中创建闭包时。例如,对于以下代码:

js
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}

它按预期记录 012。然而,如果变量在上层作用域中定义:

js
let i = 0;
for (; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}

它记录 333。原因是每个 setTimeout 都会创建一个新的闭包,该闭包围绕 i 变量形成闭包,但如果 i 不限定于循环体,则所有闭包在最终被调用时都将引用相同的变量——并且由于 setTimeout() 的异步性质,它将在循环已经退出后发生,导致所有排队回调的函数体中的 i 值为 3

如果你使用 var 语句作为初始化,也会发生这种情况,因为用 var 声明的变量只具有函数作用域,而不具有词法作用域(即,它们不能限定于循环体)。

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// Logs 3, 3, 3

初始化块的作用域效果可以理解为声明发生在循环体内部,但恰好在 conditionafterthought 部分中可访问。更准确地说,let 声明是 for 循环的特殊情况——如果 initializationlet 声明,那么每次循环体评估之后,会发生以下情况:

  1. 会创建一个新的词法作用域,其中包含新的 let 声明的变量。
  2. 上一次迭代的绑定值用于重新初始化新变量。
  3. afterthought 在新作用域中评估。

因此,在 afterthought 中重新赋值新变量不会影响上一次迭代的绑定。

initialization 之后,在 condition 首次评估之前,也会创建一个新的词法作用域。这些细节可以通过创建闭包来观察,闭包允许在任何特定点获取绑定。例如,在此代码中,在 initialization 部分中创建的闭包不会被 afterthoughti 的重新赋值更新。

js
for (let i = 0, getI = () => i; i < 3; i++) {
  console.log(getI());
}
// Logs 0, 0, 0

这不会打印 "0, 1, 2",就像 getI 在循环体中声明时会发生的那样。这是因为 getI 不会在每次迭代中重新评估——相反,该函数被创建一次并围绕 i 变量形成闭包,该变量引用在循环首次初始化时声明的变量。随后对 i 值的更新实际上会创建名为 i 的新变量,getI 看不到这些变量。解决此问题的一种方法是每次 i 更新时重新计算 getI

js
for (let i = 0, getI = () => i; i < 3; i++, getI = () => i) {
  console.log(getI());
}
// Logs 0, 1, 2

initialization 中的 i 变量与每次迭代中的 i 变量(包括第一次迭代)是不同的。因此,在此示例中,即使迭代中的 i 值提前递增,getI 也会返回 0。

js
for (let i = 0, getI = () => i; i < 3; ) {
  i++;
  console.log(getI());
}
// Logs 0, 0, 0

事实上,你可以捕获 i 变量的这个初始绑定,并稍后重新赋值,这个更新的值对循环体是不可见的,循环体看到的是 i 的下一个新绑定。

js
for (
  let i = 0, getI = () => i, incrementI = () => i++;
  getI() < 3;
  incrementI()
) {
  console.log(i);
}
// Logs 0, 0, 0

这会打印 "0, 0, 0",因为每次循环评估中的 i 变量实际上是一个单独的变量,但是 getIincrementI 都读取和写入 i 的*初始*绑定,而不是随后声明的值。

不带循环体的 for 循环

以下 for 循环在 afterthought 部分计算节点的偏移位置,因此它不需要使用 statement 部分,而是使用分号。

js
function showOffsetPos(id) {
  let left = 0;
  let top = 0;
  for (
    let itNode = document.getElementById(id); // initialization
    itNode; // condition
    left += itNode.offsetLeft,
      top += itNode.offsetTop,
      itNode = itNode.offsetParent // afterthought
  ); // semicolon

  console.log(
    `Offset position of "${id}" element:
left: ${left}px;
top: ${top}px;`,
  );
}

showOffsetPos("content");

// Logs:
// Offset position of "content" element:
// left: 0px;
// top: 153px;

请注意,for 语句后的分号是强制性的,因为它是一个空语句。否则,for 语句会将随后的 console.log 行作为其 statement 部分,这将导致 log 执行多次。

使用带有两个迭代变量的 for 循环

你可以使用逗号运算符在 for 循环中创建同时更新的两个计数器。多个 letvar 声明也可以用逗号连接。

js
const arr = [1, 2, 3, 4, 5, 6];
for (let l = 0, r = arr.length - 1; l < r; l++, r--) {
  console.log(arr[l], arr[r]);
}
// 1 6
// 2 5
// 3 4

规范

规范
ECMAScript® 2026 语言规范
# sec-for-statement

浏览器兼容性

另见