💡 この記事でわかること
- スコープ(変数が見える範囲)とは何か
- 関数がどうやってスコープを作るか
- クロージャとは何か、その仕組み
- クロージャの具体的な使い方(カウンター関数など)
- 初心者がつまずきやすいポイント
1. スコープとは?
スコープとは、
👉 「その変数がコードのどの範囲から見えるか(参照できるか)」を決めるルールのことです。
スコープには大きく分けて2種類あります。
- グローバルスコープ:どこからでも参照できる範囲(関数の外で宣言した変数)
- ローカルスコープ:特定の関数やブロックの中でしか参照できない範囲
const globalMessage = "どこからでも見える";
function showMessage() {
const localMessage = "この関数の中でしか見えない";
console.log(globalMessage); // OK
console.log(localMessage); // OK
}
showMessage();
console.log(globalMessage); // OK
console.log(localMessage); // エラー:localMessageは参照できない
💡 関数の中で宣言した変数は、その関数の外からは見えません。これを「関数がローカルスコープを作る」と表現します。
2. 関数はネストするとスコープも入れ子になる
関数の中にさらに関数を書く(ネストする)と、内側の関数からは外側の関数の変数を参照できます。
function outer() {
const outerValue = "外側の変数";
function inner() {
console.log(outerValue); // 外側の変数を参照できる
}
inner();
}
outer(); // "外側の変数"
👉 内側の関数(inner)は、自分のスコープに変数がなければ、1つ外側のスコープを探しにいきます。これを「スコープチェーン」と呼びます。クロージャは、この仕組みの上に成り立っています。
3. クロージャとは?
クロージャとは、
👉 関数が「自分が作られたときのスコープ」を覚えたまま持ち運べる仕組みのことです。
言葉だけだと分かりにくいので、実際のコードで見てみましょう。
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
💡 createCounter()の実行が終わると、本来ならcount変数は消えるはずです。しかし、内側で作られた関数がcountを参照し続けているため、countは消えずに残り続けます。
この「外側の関数が終わったあとも、内側の関数が外側の変数を覚えていて使い続けられる」状態こそがクロージャです。counterを呼ぶたびに、記憶しているcountが1ずつ増えていくのが確認できます。
4. クロージャの使いどころ:変数を外から直接触れなくする
クロージャの便利な点の1つは、
👉 変数を関数の外から直接書き換えられないように守れることです。
const counter = createCounter();
counter.count = 100; // これはcountに何の影響も与えない
console.log(counter()); // 4(前の続きから正しくカウントされる)
👉 count変数はcreateCounter関数のスコープに閉じ込められていて、外部から直接アクセスする手段がありません。用意した関数を通してしか操作できないため、意図しない値の書き換えを防げます。このような使い方を「カプセル化」と呼びます。
👉 JavaScriptのオブジェクトについての記事はこちら
5. 初心者がつまずきやすいポイント:ループとクロージャ
クロージャの理解でよくつまずくのが、ループの中で関数を作るケースです。
// varを使った場合
const funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 3(0を期待していたのに...)
funcs[1](); // 3
funcs[2](); // 3
💡 varはループ全体で1つの変数iを共有するため、すべての関数が同じiを参照してしまいます。ループが終わったときのiの値(3)を、あとから全員が見ることになるのです。
letを使うと、ループの1回ごとに新しいスコープのiが作られるため、期待通りの結果になります。
// letを使った場合
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2
👉 「ループの中で作った関数が、みんな同じ値を参照してしまう」というバグに出会ったら、varをletに変えることで解決できる場合が多いので覚えておきましょう。
まとめ
- スコープとは、変数が参照できる範囲のこと
- 関数の中で関数を作ると、内側の関数は外側の変数を参照できる
- クロージャとは、関数が作られたときのスコープを覚えたまま持ち運べる仕組み
- 変数を外から直接触れなくする「カプセル化」に活用できる
- ループの中でvarを使うと、クロージャが全員同じ値を参照してしまうので注意
クロージャは最初は少し難しく感じますが、「関数は自分が生まれた場所の変数を覚えている」というイメージさえつかめれば、コードを読むときの理解がぐっと深まります。実際に手を動かしてコードを書き換えながら試してみましょう。