for
for
语句创建一个循环,包含三个可选表达式,用括号括起来并用分号隔开,后面跟着一个语句(通常是 块语句),将在循环中执行。
试试看
语法
for (initialization; condition; afterthought)
statement
initialization
可选-
一个表达式(包括 赋值表达式)或变量声明,在循环开始前评估一次。通常用于初始化计数器变量。此表达式可以选择使用
var
或let
关键字声明新变量。用var
声明的变量不是循环局部变量,也就是说,它们与for
循环所在的范围相同。用let
声明的变量是语句的局部变量。此表达式的结果将被丢弃。
condition
可选-
在每次循环迭代之前评估的表达式。如果此表达式 评估为真,则执行
statement
。如果表达式 评估为假,则执行将退出循环,并转到for
结构后的第一个语句。此条件测试是可选的。如果省略,则条件始终评估为真。
afterthought
可选-
在每次循环迭代结束时评估的表达式。这发生在下次评估
condition
之前。通常用于更新或递增计数器变量。 statement
描述
例子
使用 for
以下 for
语句首先声明变量 i
并将其初始化为 0
。它检查 i
是否小于九,执行接下来的两个语句,并在每次循环通过后将 i
递增 1。
for (let i = 0; i < 9; i++) {
console.log(i);
// more statements
}
初始化块语法
初始化块接受表达式和变量声明。但是,表达式不能使用 in
运算符(不带括号),因为这与 for...in
循环有歧义。
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);
}
可选 for 表达式
for
循环头部中的所有三个表达式都是可选的。例如,不需要使用 initialization
块来初始化变量
let i = 0;
for (; i < 9; i++) {
console.log(i);
// more statements
}
与 initialization
块类似,condition
部分也是可选的。如果您省略此表达式,则必须确保在循环体中中断循环,以避免创建无限循环。
for (let i = 0; ; i++) {
console.log(i);
if (i > 3) break;
// more statements
}
您也可以省略所有三个表达式。同样,请确保使用 break
语句来结束循环,并修改(增加)变量,以便 break 语句的条件在某个时刻为真。
let i = 0;
for (;;) {
if (i > 3) break;
console.log(i);
i++;
}
但是,如果您没有完全使用所有三个表达式位置 - 尤其是在您没有使用第一个表达式声明变量,而是修改了上层范围中的某些内容 - 请考虑使用 while
循环,这样更清晰地表达了意图。
let i = 0;
while (i <= 3) {
console.log(i);
i++;
}
初始化块中的词法声明
在初始化块中声明变量与在上层 范围 中声明变量有重要区别,尤其是在循环体中创建 闭包 时。例如,对于下面的代码
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
它按预期输出 0
、1
和 2
。但是,如果在更高的范围中定义变量
let i = 0;
for (; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
它输出 3
、3
和 3
。原因是每个 setTimeout
都创建一个新的闭包,该闭包关闭了 i
变量,但是如果 i
没有限定在循环体范围内,那么所有闭包将在它们最终被调用时引用同一个变量 - 并且由于 setTimeout
的异步性质,这将在循环已退出后发生,导致所有排队的回调体中的 i
值为 3
。
如果使用 var
语句作为初始化,也会发生这种情况,因为用 var
声明的变量仅是函数范围的,而不是词法范围的(也就是说,它们不能限定在循环体范围内)。
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Logs 3, 3, 3
初始化块的范围效果可以理解为,声明是在循环体中进行的,但恰巧可以在 condition
和 afterthought
部分访问。更准确地说,let
声明由 for
循环进行特殊处理 - 如果 initialization
是 let
声明,那么每次在评估循环体后,都会发生以下情况
- 使用新的
let
声明的变量创建一个新的词法范围。 - 使用上一次迭代的绑定值重新初始化新变量。
afterthought
在新的范围内进行评估。
因此,在 afterthought
中重新分配新变量不会影响上次迭代的绑定。
在 initialization
之后,就在第一次评估 condition
之前,也会创建一个新的词法范围。这些细节可以通过创建闭包来观察,闭包允许在任何特定点获取绑定。例如,在这段代码中,在 initialization
部分创建的闭包不会因 afterthought
中 i
的重新分配而更新
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
。
for (let i = 0, getI = () => i; i < 3; i++, getI = () => i) {
console.log(getI());
}
// Logs 0, 1, 2
initialization
内部的i
变量与每次迭代(包括第一次)内部的i
变量不同。因此,在此示例中,即使迭代内部的i
值在之前已递增,getI
仍会返回0。
for (let i = 0, getI = () => i; i < 3; ) {
i++;
console.log(getI());
}
// Logs 0, 0, 0
事实上,您可以捕获i
变量的初始绑定并在稍后重新赋值,而此更新后的值将不会对循环体可见,循环体会看到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
的初始绑定,而不是随后声明的绑定。
使用不带主体的for循环
以下for
循环计算afterthought
部分中节点的偏移位置,因此不需要使用statement
部分,而是使用分号代替。
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循环中创建两个同时更新的计数器。多个let
和var
声明也可以用逗号连接。
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 表格仅在浏览器中加载