JavaScript | ES6+ 文法:変数・宣言の進化 – for 文での let

JavaScript
スポンサーリンク

for 文での let とは何か

for 文での let は「ループの各反復ごとに“新しい束縛”を作る」宣言です。ここが重要です:i のようなインデックスを let で宣言すると、反復ごとに独立した値がクロージャ(非同期コールバック)にも正しく閉じ込められます。ES5 の var で起きがちだった「全部同じ値」問題を根本から解消します。

for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 0); // 1, 2, 3
}
JavaScript

var との違い(最後の値を共有する事故を防ぐ)

var は“関数スコープ”、反復で束縛が共有される

var の i はループ全体で1つの変数を共有するため、非同期コールバックでは“最後の i”(終了後の値)になりがちです。

for (var j = 1; j <= 3; j++) {
  setTimeout(() => console.log(j), 0); // 4, 4, 4(終了後の j を参照)
}
JavaScript

let は“ブロックスコープ”、反復ごとに新しい束縛

let の i は各周回で独立するため、期待通りの値が保持されます。ここが重要です:非同期やイベントハンドラで安全に使えます。

for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 0); // 1, 2, 3(周回ごとに別の i)
}
JavaScript

ループヘッダでの let とスコープ(外側から見えない)

ループ変数はループブロックに限定される

for (let i = …) の i はループのブロックに閉じ、外から参照できません。スコープ汚染を防げます。

for (let i = 0; i < 2; i++) {}
// console.log(i); // ReferenceError(ループ外からは見えない)
JavaScript

ループ内の“その都度の一時値”もブロックで守る

反復ごとの計算結果や定数はループ内で宣言し、外へ漏らさないと意図が明確になります。

let total = 0;
for (let i = 0; i < items.length; i++) {
  const price = items[i].price ?? 0; // 反復専用の一時値
  total += price;
}
JavaScript

TDZ(Temporal Dead Zone)と宣言順の注意

同名の再宣言は避ける、宣言は使う直前

ループ本体でヘッダと同名の let/const を再宣言するとエラーになります。ここが重要です:変数名は使い分け、宣言は使う直前に置く。

for (let i = 0; i < 3; i++) {
  // const i = 99; // SyntaxError(同スコープで再宣言不可)
}
JavaScript

for-of / for-in と let/const の使い分け

変更しない反復値は const、カウンタや累積は let

for-of/for-in では各反復ごとに新しい束縛が作られます。“その周回の値を変えない”なら const が最適です。集計など“変える値”は別変数を let で持ちます。

let sum = 0;
for (const n of [1, 2, 3]) {
  sum += n;               // sum は可変なので let
}
JavaScript

非同期・イベントで効く具体例(重要ポイントの深掘り)

setTimeout や addEventListener と相性が良い

反復ごとに作るコールバックが“そのときの i”を持ち続けます。UIイベントを配列で束ねるときにも安全です。

const buttons = document.querySelectorAll("button");
for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", () => {
    console.log("clicked index:", i); // クリックしたボタンの i が正しく出る
  });
}
JavaScript

並列処理の発火順が変わっても安心

ネットワークやタイマーで完了順が前後しても、各タスクが“開始時の i”を保持しているため取り違えが起きません。

for (let i = 0; i < urls.length; i++) {
  fetch(urls[i]).then(() => console.log("done:", i)); // どの順でも正しい i
}
JavaScript

実務の指針(for での let をもっと安全に)

原則は const、必要なときだけ let(ループでも同じ)

反復値は const、カウンタや累積は let。ループ変数の寿命をループ内に閉じて、外へ漏らさない。

let total = 0;
for (let i = 0; i < rows.length; i++) {
  const r = rows[i];
  total += r.price ?? 0;
}
JavaScript

破壊的操作は避け、非破壊で扱う

ループ中に配列を sort/splice などで壊すと、インデックスや走査が破綻します。結果だけ別配列に集める、toSorted や toSpliced を使うのが安全です。

const out = [];
for (let i = 0; i < rows.length; i++) {
  const r = rows[i];
  if (r.active) out.push({ id: r.id, price: r.price });
}
// out は新規、rows は保つ
JavaScript

例題で理解を固める

var では失敗、let なら成功の対比

// 失敗例(var)
for (var j = 1; j <= 3; j++) {
  setTimeout(() => console.log("var:", j), 0); // 4, 4, 4
}

// 成功例(let)
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log("let:", i), 0); // 1, 2, 3
}
JavaScript

for-of と const の組み合わせ

let countActive = 0;
for (const u of users) {          // 反復値 u は変えない
  if (u.active) countActive++;    // 累積は let
}
JavaScript

まとめ

for 文での let の核心は「反復ごとに新しい束縛を作り、非同期でも正しく値を保持する」ことです。var では共有束縛で最後の値が混入しがちですが、let はブロックスコープでそれを防ぎます。実務では“反復値は const、カウンタ・累積は let”、宣言は使う直前、配列は非破壊で扱う。これを徹底すれば、初心者でもループと非同期の組み合わせで安全に意図どおりのコードを書けます。

タイトルとURLをコピーしました