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
for (initialization; condition; afterthought)
statement
initialization
Optional-
在循环开始之前计算一次的表达式(包括 赋值表达式)或变量声明。通常用于初始化计数器变量。该表达式可以选择使用
var
或let
关键字声明新变量。用var
声明的变量不是循环的本地变量,即它们与for
循环位于同一范围内。用let
声明的变量是语句的局部变量。该表达式的结果将被丢弃。
condition
Optional-
每次循环迭代之前要计算的表达式。如果这个表达式 评估结果为真,
statement
被执行。如果表达式 评估结果为假,则执行退出循环并转到for
构造之后的第一条语句。此条件测试是可选的。如果省略,则条件始终评估为 true。
afterthought
Optional-
在每次循环迭代结束时计算的表达式。这发生在下一次评估
condition
之前。一般用于更新或递增计数器变量。 statement
-
只要条件为 true 就执行的语句。你可以使用 块语句 来执行多个语句。要在循环内不执行任何语句,请使用 空语句 (
;
)。
描述
¥Description
与其他循环语句一样,你可以在 statement
内使用 控制流语句:
¥Like other looping statements, you can use control flow statements inside statement
:
示例
用于
¥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.
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.
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.
// 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:
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.
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.
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.
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:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
正如预期的那样,它记录了 0
、1
和 2
。但是,如果变量是在上层作用域中定义的:
¥It logs 0
, 1
, and 2
, as expected. However, if the variable is defined in the upper scope:
let i = 0;
for (; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
它记录 3
、3
和 3
。原因是每个 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).
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Logs 3, 3, 3
初始化块的作用域效果可以理解为声明发生在循环体内,但恰好可以在 condition
和 afterthought
部分中访问。更准确地说,let
声明是 for
循环的特例 — 如果 initialization
是 let
声明,则每次在循环体求值后,都会发生以下情况:
¥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:
- 使用新的
let
声明的变量创建新的词法范围。 - 上次迭代的绑定值用于重新初始化新变量。
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
:
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:
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:
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
.
for (
let i = 0, getI = () => i, incrementI = () => i++;
getI() < 3;
incrementI()
) {
console.log(i);
}
// Logs 0, 0, 0
这会记录 "0, 0, 0",因为每个循环求值中的 i
变量实际上是一个单独的变量,但 getI
和 incrementI
都读取和写入 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.
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 循环中同时更新的计数器。多个 let
和 var
声明也可以用逗号连接。
¥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.
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 |
浏览器兼容性
BCD tables only load in the browser