for

for 语句创建一个循环,包含三个可选表达式,用括号括起来并用分号隔开,后面跟着一个语句(通常是 块语句),将在循环中执行。

试试看

语法

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 (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 语句来结束循环,并修改(增加)变量,以便 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 语句作为初始化,也会发生这种情况,因为用 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

这不会像在循环体中声明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());
}
// 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 语言规范
# sec-for-statement

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅