for

for 文は、括弧で囲みセミコロンで区切った 3 つの引数と、続いてループ内で実行される文(ふつうはブロック文)から成るループを構成します。

試してみましょう

構文

js
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 増加します。

js
for (let i = 0; i < 9; i++) {
  console.log(i);
  // その他の文
}

初期化ブロックの構文

初期化ブロックは、式と変数宣言の両方を受け入れることができます。ただし、式には括弧で囲んでいない 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 ループの先頭にある 3 つの式は、省略可能です。例えば、initialization ブロックで変数を初期化する必要はありません。

js
let i = 0;
for (; i < 9; i++) {
  console.log(i);
  // その他の文
}

initialization ブロックと同様に、condition ブロックも省略可能です。この式を省略した場合は、本体の中でループを脱出できるようにして、無限ループにならないようにしなければなりません。

js
for (let i = 0; ; i++) {
  console.log(i);
  if (i > 3) break;
  // その他の文
}

3 つのブロックをすべて省略することもできます。繰り返しますが、 break 文を使用してループを終了させ、また break 文の条件がある時点で true になるように、変数を変更(増加)させていることを確認してください。

js
let i = 0;

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

しかし、3 つの位置の式をすべて使用する訳ではない場合、特に最初の式で変数を宣言せず、上位のスコープで何かを変更している場合は、代わりに 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 とログ出力します。なぜかと言うと、それぞれの setTimeouti 変数を閉じる新しいクロージャを作成しますが、i がループ本体のスコープでない場合、すべてのクロージャは最終的に呼び出されたときに同じ変数を参照します。そして setTimeout の非同期であるため、すでにループが終了した後に実行され、すべてのキューのコールバック本体の i3 という値になります。

これは、初期化に var 文を使用した場合にも起こります。var で宣言された変数は関数スコープのみで、レキシカルスコープにならない(つまり、ループ本体のスコープにすることはできない)からです。

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// 3, 3, 3 とログ出力

初期化ブロックのスコープ効果は、宣言がループ本体の中で行われ、たまたま conditionafterthought の部分でアクセス可能であるかのように理解することができます。より正確には、let宣言は for ループに特化されます。もし initializationlet 宣言であれば、ループ本体が評価された後、以下のことが毎回行われます。

  1. 新しい字句スコープが作成され、新しい let が宣言された変数が追加されます。
  2. 前回の反復処理でバインドされた値を用いて、新しい変数を再初期化します。
  3. afterthought が新しいスコープで評価されます。

そのため、afterthought 内で新しい変数を割り当てても、前回反復処理したときのバインディングには影響しません。

クロージャを作成することで、特定の反復処理中のバインディングを取得することができます。このことから、initialization 節で作成したクロージャが、afterthought 節で i を再代入しても更新されないことがわかります。

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

実際、変数 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 の繰り返しでは、 final-expression 句の中でにおけるノードのオフセット位置を検索しています。 statement 節を使用する必要がない場合は、代わりにセミコロンを使用してください。

js
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 つのカウンターを作成することができます。複数の 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

仕様書

Specification
ECMAScript Language Specification
# sec-for-statement

ブラウザーの互換性

BCD tables only load in the browser

関連情報