for

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

¥The for statement creates a loop that consists of three optional expressions, enclosed in parentheses and separated by semicolons, followed by a statement (usually a block statement) to be executed in the loop.

Try it

语法

¥Syntax

js
for (initialization; condition; afterthought)
  statement
initialization Optional

在循环开始之前计算一次的表达式(包括 赋值表达式)或变量声明。通常用于初始化计数器变量。该表达式可以选择使用 varlet 关键字声明新变量。用 var 声明的变量不是循环的本地变量,即它们与 for 循环位于同一范围内。用 let 声明的变量是语句的局部变量。

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

condition Optional

每次循环迭代之前要计算的表达式。如果这个表达式 评估结果为真statement 被执行。如果表达式 评估结果为假,则执行退出循环并转到 for 构造之后的第一条语句。

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

afterthought Optional

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

statement

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

示例

¥Examples

用于

¥Using for

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

¥The following for statement starts by declaring the variable i and initializing it to 0. It checks that i is less than nine, performs the two succeeding statements, and increments i by 1 after each pass through the loop.

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

初始化块语法

¥Initialization block syntax

初始化块接受表达式和变量声明。但是,表达式不能使用不带括号的 in 运算符,因为这对于 for...in 循环来说是不明确的。

¥The initialization block accepts both expressions and variable declarations. However, expressions cannot use the in operator unparenthesized, because that is ambiguous with a for...in loop.

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

表达式可选

¥Optional for expressions

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

¥All three expressions in the head of the for loop are optional. For example, it is not required to use the initialization block to initialize variables:

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

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

¥Like the initialization block, the condition part is also optional. If you are omitting this expression, you must make sure to break the loop in the body in order to not create an infinite loop.

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

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

¥You can also omit all three expressions. Again, make sure to use a break statement to end the loop and also modify (increase) a variable, so that the condition for the break statement is true at some point.

js
let i = 0;

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

但是,如果你没有完全使用所有三个表达式位置(尤其是如果你没有使用第一个表达式声明变量而是在上部作用域中更改某些内容),请考虑使用 while 循环,这使得意图更清晰。

¥However, in the case where you are not fully using all three expression positions — especially if you are not declaring variables with the first expression but mutating something in the upper scope — consider using a while loop instead, which makes the intention clearer.

js
let i = 0;

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

初始化块中的词法声明

¥Lexical declarations in the initialization block

在初始化块中声明变量与在上部 scope 中声明变量有重要区别,尤其是在循环体内创建 closure 时。例如,对于下面的代码:

¥Declaring a variable within the initialization block has important differences from declaring it in the upper scope, especially when creating a closure within the loop body. For example, for the code below:

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

正如预期的那样,它记录了 012。但是,如果变量是在上层作用域中定义的:

¥It logs 0, 1, and 2, as expected. However, if the variable is defined in the upper scope:

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

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

¥It logs 3, 3, and 3. The reason is that each setTimeout creates a new closure that closes over the i variable, but if the i is not scoped to the loop body, all closures will reference the same variable when they eventually get called — and due to the asynchronous nature of setTimeout, it will happen after the loop has already exited, causing the value of i in all queued callbacks' bodies to have the value of 3.

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

¥This also happens if you use a var statement as the initialization, because variables declared with var are only function-scoped, but not lexically scoped (i.e. they can't be scoped to the loop body).

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

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

¥The scoping effect of the initialization block can be understood as if the declaration happens within the loop body, but just happens to be accessible within the condition and afterthought parts. More precisely, let declarations are special-cased by for loops — if initialization is a let declaration, then every time, after the loop body is evaluated, the following happens:

  1. 使用新的 let 声明的变量创建新的词法范围。
  2. 上次迭代的绑定值用于重新初始化新变量。
  3. afterthought 在新范围内进行评估。

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

¥So re-assigning the new variables within afterthought does not affect the bindings from the previous iteration.

initialization 之后、第一次计算 condition 之前,还会创建一个新的词法作用域。可以通过创建闭包来观察这些细节,闭包允许在任何特定点获取绑定。例如,在此代码中,在 initialization 部分中创建的闭包不会通过在 afterthought 中重新分配 i 来更新:

¥A new lexical scope is also created after initialization, just before condition is evaluated for the first time. These details can be observed by creating closures, which allow to get hold of a binding at any particular point. For example, in this code a closure created within the initialization section does not get updated by re-assignments of i in the afterthought:

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

¥This does not log "0, 1, 2", like what would happen if getI is declared in the loop body. This is because getI is not re-evaluated on each iteration — rather, the function is created once and closes over the i variable, which refers to the variable declared when the loop was first initialized. Subsequent updates to the value of i actually create new variables called i, which getI does not see. A way to fix this is to re-compute getI every time i updates:

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

initialization 内的 i 变量与每次迭代(包括第一次)内的 i 变量不同。因此,在此示例中,getI 返回 0,即使迭代内 i 的值已预先递增:

¥The i variable inside the initialization is distinct from the i variable inside every iteration, including the first. So, in this example, getI returns 0, even though the value of i inside the iteration is incremented beforehand:

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

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

¥In fact, you can capture this initial binding of the i variable and re-assign it later, and this updated value will not be visible to the loop body, which sees the next new binding of 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 的初始绑定,而不是随后声明的内容。

¥This logs "0, 0, 0", because the i variable in each loop evaluation is actually a separate variable, but getI and incrementI both read and write the initial binding of i, not what was subsequently declared.

用于无主体

¥Using for without a body

接下来的 for 循环计算的是节点在 afterthought 段中的偏移位置,因此不需要使用 statement 段,而是用分号代替。

¥The following for cycle calculates the offset position of a node in the afterthought section, and therefore it does not require the use of a statement section, a semicolon is used instead.

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 执行多次。

¥Note that the semicolon after the for statement is mandatory, because it stands as an empty statement. Otherwise, the for statement acquires the following console.log line as its statement section, which makes the log execute multiple times.

将 for 与两个迭代变量一起使用

¥Using for with two iterating variables

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

¥You can create two counters that are updated simultaneously in a for loop using the comma operator. Multiple let and var declarations can also be joined with commas.

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

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看