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
文は、括弧で囲みセミコロンで区切った 3 つの引数と、続いてループ内で実行される文(ふつうはブロック文)から成るループを構成します。
試してみましょう
構文
for (initialization; condition; afterthought)
statement
initialization
省略可-
ループが始まる前に一度だけ評価される(代入式を含む)式または変数宣言。ふつうはカウンター変数を初期化するために使われます。この式では任意で、
var
キーワードを用いて新しい変数を宣言することもできます。var
で宣言された変数はループ内のローカル変数にはなりません。すなわち、for
ループが属するスコープと同じスコープになります。let
で宣言された変数は文内のローカル変数になります。この式の結果は捨て去られます。
condition
省略可-
ループのそれぞれの反復処理が行われる前に評価される式です。この式が true と評価された場合は、
statement
が実行されます。この式が false と評価された場合は、実行はfor
構造に続く最初の式に飛びます。この条件テストはオプションです。省略した場合、この条件は常に true と評価されます。
afterthought
省略可-
ループのそれぞれの反復処理の最後に評価される式です。これは、次の
condition
の評価前に行われます。一般的には、カウンター変数を更新または増加するために使われます。 statement
-
条件が true と評価された場合に限り実行される文です。ループ内で複数の文を実行するには、ブロック文を使用して文をグループ化してください。ループ内で文を実行しないようにするには、空文 (
;
) を使用してください。
例
for の使用
次の for
文は、変数 i
を宣言し、それを 0
に初期化することから始まります。i
が 9 より小さいことをチェックし、続く 2 つの文を実行し、ループを通過した後ごとに i
を 1 増加します。
for (let i = 0; i < 9; i++) {
console.log(i);
// その他の文
}
初期化ブロックの構文
初期化ブロックは、式と変数宣言の両方を受け入れることができます。ただし、式には括弧で囲んでいない 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
ループの先頭にある 3 つの式は、省略可能です。例えば、initialization
ブロックで変数を初期化する必要はありません。
let i = 0;
for (; i < 9; i++) {
console.log(i);
// その他の文
}
initialization
ブロックと同様に、condition
ブロックも省略可能です。この式を省略した場合は、本体の中でループを脱出できるようにして、無限ループにならないようにしなければなりません。
for (let i = 0; ; i++) {
console.log(i);
if (i > 3) break;
// その他の文
}
3 つのブロックをすべて省略することもできます。繰り返しますが、 break
文を使用してループを終了させ、また break 文の条件がある時点で true になるように、変数を変更(増加)させていることを確認してください。
let i = 0;
for (;;) {
if (i > 3) break;
console.log(i);
i++;
}
しかし、3 つの位置の式をすべて使用する訳ではない場合、特に最初の式で変数を宣言せず、上位のスコープで何かを変更している場合は、代わりに 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);
}
// 3, 3, 3 とログ出力
初期化ブロックのスコープ効果は、宣言がループ本体の中で行われ、たまたま condition
と afterthought
の部分でアクセス可能であるかのように理解することができます。より正確には、let
宣言は for
ループに特化されます。もし initialization
が let
宣言であれば、ループ本体が評価された後、以下のことが毎回行われます。
- 新しい字句スコープが作成され、新しい
let
が宣言された変数が追加されます。 - 前回の反復処理でバインドされた値を用いて、新しい変数を再初期化します。
afterthought
が新しいスコープで評価されます。
そのため、afterthought
内で新しい変数を割り当てても、前回反復処理したときのバインディングには影響しません。
クロージャを作成することで、特定の反復処理中のバインディングを取得することができます。このことから、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
実際、変数 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
の繰り返しでは、 final-expression
句の中でにおけるノードのオフセット位置を検索しています。 statement
節を使用する必要がない場合は、代わりにセミコロンを使用してください。
function showOffsetPos(id) {
let left = 0;
let top = 0;
for (
let itNode = document.getElementById(id); // 初期化
itNode; // 条件式
left += itNode.offsetLeft,
top += itNode.offsetTop,
itNode = itNode.offsetParent // 更新式
); // セミコロン
console.log(
`Offset position of "${id}" element:
left: ${left}px;
top: ${top}px;`,
);
}
showOffsetPos("content");
// 出力結果:
// Offset position of "content" element:
// left: 0px;
// top: 153px;
for
文の後のセミコロンは必須であることに注意してください。これは空文としての役割を果たすからです。そうしないと、for
文は以下の console.log
行を statement
節として取得し、log
を複数回実行させることになる。
2 つの反復用変数の使用
カンマ演算子を用いて、for ループで同時に更新される 2 つのカウンターを作成することができます。複数の 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
仕様書
Specification |
---|
ECMAScript Language Specification # sec-for-statement |
ブラウザーの互換性
BCD tables only load in the browser