Code Fix

中級

forループのvarをletに変えないとコールバックが同じ値を指す

varはブロックスコープを持たないため、ループ内で生成した関数はすべて同じ変数を共有します。ループが終わったあとに関数を呼び出すと、全て最後の値を参照します。letはループの繰り返しごとに新しい束縛を作るため、この問題が起きません。

なぜエラーが出ないのか

出力: 3 3 3
(エラーなし)
JavaScriptは何も報告しません。文法として正しいためです
出力: 3 3 3
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  function makeGetters() {
 2    const getters = [];
 3    for (var   i = 0; i < 3; i++) {
             ^
 4      getters.push(function() { return i; });
 5    }
 6    return getters;
 7  }
 8  const getters = makeGetters();
 9  console.log(getters[0](), getters[1](), getters[2]());
出力: 3 3 3期待: 0 1 2 (エラーにはなりません)

varはブロックスコープを持たないため、3つの関数は同じiを共有します。呼び出された時点でのiの値(ループ終了後の3)を全員が参照します。letはループのたびに新しい束縛を作るため、それぞれの関数が自分の回のiを覚えます。

直し方: varlet にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  function makeMultipliers() {
 2    const fns = [];
 3    for (var   i = 1; i <= 3; i++) {
             ^
 4      fns.push((x) => x * i);
 5    }
 6    return fns;
 7  }
 8  const fns = makeMultipliers();
 9  console.log(fns[0](10), fns[1](10), fns[2](10));
出力: 40 40 40期待: 10 20 30 (エラーにはなりません)

varで宣言したiはループ終了後4になっており、3つの関数すべてがその4を参照してしまいます。letなら各回ごとに独立したiが使われます。

直し方: varlet にします。

この問題を解いてみる →

パターン3

 1  function makeLabelers(labels) {
 2    const fns = [];
 3    for (var   i = 0; i < labels.length; i++) {
             ^
 4      fns.push(() => labels[i]);
 5    }
 6    return fns;
 7  }
 8  const fns = makeLabelers(["a", "b", "c"]);
 9  console.log(fns[0](), fns[1](), fns[2]());
出力: undefined undefined undefined期待: a b c (エラーにはなりません)

ループ終了後、varのiは3(配列の範囲外)になっており、labels[3]はundefinedです。letならそれぞれの関数が自分の回のインデックスを覚えます。

直し方: varlet にします。

この問題を解いてみる →

よくある誤解

「ループの中で作った関数はその時点のiの値を覚えている」という思い込みは、varを使う限り成り立ちません。関数が実際に呼ばれる時点でのiの値(多くの場合ループ終了後の値)を参照します。

まとめ

forループのvarをletに変えないとコールバックが同じ値を指すは中級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)