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);
    

    然而,如果你没有完全使用这三个表达式,尤其是如果你没有使用第一个表达式声明变量,而是在上层作用域中修改了某个东西,那么考虑使用 while 循环,这样可以更清晰地说明意图。

    js
    let i = 0;
    while (i <= 3) {
      console.log(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 声明,那么每次循环体执行完毕后,都会发生以下事情:

  • 使用 let 声明新的变量会创建一个新的词法作用域。
  • 上次迭代的绑定值用于重新初始化新变量。
  • 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; ) {
      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 语句

    你可以使用 逗号运算符 来创建两个同时更新的计数器。多个 let var 声明也可以使用逗号运算符连接。

    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® 2026 Language Specification
    # sec-for-statement

    浏览器兼容性

    参见