for

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

for 语句用于创建一个循环,它包含了三个可选的表达式,这三个表达式被包围在圆括号之中,使用分号分隔,后跟一个用于在循环中执行的语句(通常是一个块语句)。

尝试一下

let str = "";

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

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

语法

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

在循环开始前初始化的表达式(包含赋值表达式)或者变量声明。通常用于初始化计数器变量。该表达式可以选择使用 varlet 关键字声明新的变量,使用 var 声明的变量不是该循环的局部变量,而是与 for 循环处在同样的作用域中。用 let 声明的变量是语句的局部变量。

该表达式的结果会被丢弃。

condition 可选

每次循环迭代之前要判定的表达式。如果该表达式的判定结果为真statement 将被执行。如果判定结果为假,那么执行流程将退出循环,并转到 for 结构后面的第一条语句。

这个条件测试是可选的。如果省略,该条件总是计算为真。

afterthought 可选

每次循环迭代结束时执行的表达式。执行时机是在下一次判定 condition 之前。通常被用于更新或者递增计数器变量。

statement

只要条件的判定结果为真就会被执行的语句。你可以使用块语句来执行多个语句。如果没有任何语句要执行,请使用一个空语句;)。

描述

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

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

示例

使用 for

下面的 for 语句首先声明了变量 i 并被将其初始化为 0。它检查 i 是否小于九,执行两个后续语句,并在每次循环后将 i 增加 1。

js
for (var i = 0; i < 9; i++) {
  console.log(i);
  // 更多语句
}

初始化块的语法

初始化块接受表达式和变量声明。然而,表达式不能使用没有括号的 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
// 将整个初始化器括起来
for (let i = ("start" in window ? window.start : 0); i < 9; i++) {
  console.log(i);
}

// 将 `in` 表达式括起来
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);
  // 更多语句
}

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

js
for (let i = 0; ; i++) {
  console.log(i);
  if (i > 3) break;
  // 更多语句
}

你也可以忽略所有的表达式。同样地,确保使用了 break 语句来结束循环并修改(递增)变量,使得中断语句的条件在某个时刻为真。

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 语句来初始化,那么变量声明将只作用于函数作用域,而不是词法作用域(即它不会局限于循环体)。

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// 打印 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());
}
// 打印 0、0、0

这不会像在循环体中声明 getI 的那样,打印“0、1、2”。这是因为 getI 在每次迭代时都不会重新计算——相反,它是一次性创建的,并引用了 i 变量,该变量是在循环首次初始化时声明的。对 i 的后续更新实际上会创建新的变量 i,而 getI 却看不到。修复的方法是每当 i 更新时重新计算 getI

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

initialization 内部的 i 变量与每次迭代中的 i 变量是不同的,包括第一次。因此,在这个例子中,getI 返回 0,即使在迭代中 i 的值已经递增了:

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

实际上,你可以捕获 i 变量的初始绑定,并稍后重新分配它,这样更新的值不会影响循环体,它只能看到下一次的 i 绑定。

js
for (
  let i = 0, getI = () => i, incrementI = () => i++;
  getI() < 3;
  incrementI()
) {
  console.log(i);
}
// 打印 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
  ); // 分号

  console.log(
    `${id}”元素的偏移位置:
左侧:${left}px;
顶部:${top}px;`,
  );
}

showOffsetPos("content");

// 打印:
// “content”元素的偏移位置
// 左侧:0px;
// 顶部:153px;

注意,for 语句后面必须使用分号,因为它是一个空语句。否则,for 语句将 console.log 行作为 statement 部分,这会导致 log 被执行多次。

使用两个迭代变量的 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

规范

Specification
ECMAScript® 2025 Language Specification
# sec-for-statement

浏览器兼容性

Report problems with this compatibility data on GitHub
desktopmobileserver
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
Deno
Node.js
for

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support

参见