こんにちは!Reiです(˵ •̀ ᴗ – ˵) ✧
クロージャって、一度は聞いたことがあるけど、いざ説明しようとすると少し曖昧になりやすいですよね 💦
「値が残る」「関数が覚えている」あたりはなんとなくわかるけど、実際にコードの中でどう動いているのかは整理しきれていない…という方も多いと思います 👀
今回は、そんなクロージャの動きをクイズ形式で整理していきます!
コードを順番に追いながら、「どうしてそうなるのか」を一つずつ確認していきましょう ᥫᩣ ̖́-
第1問 クロージャで値は残る?
まずはここから!
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
このコードを実行したとき、 表示される数字はどうなるでしょうか?
- 毎回
1が表示される 1, 2, 3と増えていく- エラーになる
正解は… (ここをクリック!)
2の「1, 2, 3 と増えていく」です!
解説 !!
早速、ここがクロージャのいちばん大事なポイント 💡
まず、このコードの流れを順番に見ていきます 👀
const counter = createCounter();
この時点で、count が 0 の状態で関数が作られます。そして、count を増やしていく関数が counter に入ります。
次に、counter()を呼び出し1回目の実行を行います。
counter(); // 1回目の実行
この時、createCounter 内の count が 1 になります。
counter(); // 2回目の実行
さっきの続きで、count が 2 になります。
counter(); // 3回目の実行
さらに続きで、count が 3 になります。
ここでポイントになるのが、count が毎回リセットされるのではなく、前回の値を引き継いで使われている という点です。
普通は、関数を呼び出すたびに、その中の変数は新しく作られますよね。
でも今回の場合は、return で関数を外に出しています。
このとき作られるのは「関数」だけではなく、「count を持った関数」です。
つまり、「関数だけ」ではなく、「count を含んだ状態の関数」が外に出ているイメージです。
そのため、count は同じものが使われ続けて、1 → 2 → 3 と更新された値をそのまま参照できるんです。
このように、関数を作ったときの値がそのまま残り、その値を使い続けながら処理できる、この仕組みを クロージャ といいます 💡
第2問 count はどこにある?
次はこちら!
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
}
};
}
const counter = createCounter();
このとき、 外側から count の値を直接 100 に書き換えることはできるでしょうか?
- できる
- できない
increment()を1回呼んだあとだけできる
正解は… (ここをクリック!)
2の「できない」です!
解説 !!
count は createCounter() の中で定義されています。
この時点で、外に出ているのは counter だけで、count 自体は外には出ていません。
そのため、
counter.count = 100;
のように書いても、もともとの count を書き換えることはできません。
これは、counter が持っているのは「関数(increment)」だけで、count そのものは外から直接触れない場所にあるためです。
つまりこの状態は、「値は中に持っているけど、外から直接触れない」という形になっています。
このように、必要な操作だけ外に出して、値そのものは外から触れないようにすることで、意図しない書き換えを防ぐことができるんです。
「値は持っていたいけど、外から勝手に変えられたくない」
こんなときにクロージャが役立つんです 💡
第3問 var のループで何が起こる?
次はこちら!
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
このコードを実行したとき、 1秒後に表示される数字はどうなるでしょうか?
0, 0, 00, 1, 23, 3, 3
正解は… (ここをクリック!)
3 の「3, 3, 3」です!
解説 !!
このコードのポイントは、「いつ i が使われるか」です。
setTimeout はその場で処理を実行するのではなく、「あとで実行する処理」としてJavaScriptの待機リスト(キュー)に登録されるため、今回の for 文のループはsetTimeoutの実行を待たずに処理を進めます。
そのため、
for (var i = 0; i < 3; i++) {
のループはそのまま最後まで実行され、i はこのように変わります↓
0 → 1 → 2 → 3
このとき、ループ内では setTimeout が3回実行されており、「あとで実行する処理」が3つ登録された状態になっています。
そして、登録された
setTimeout(function () {
console.log(i);
}, 1000);
の setTimeout の処理が実行されるときには、この時点ですでに i は 3 になっています。
ここで重要なのは、この3つの関数がそれぞれ別の i を持っているわけではない、という点です。
3つとも同じ i を見ているため、
3, 3, 3
と表示されます。
つまり、関数はその時の値を覚えているのではなく、同じ変数を見に行っているという点がポイントです 💡
第4問 クロージャは何に使うの?
最後の問題はこちら!
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add = makeAdder(5);
console.log(add(2));
このコードの結果はどうなるでしょうか?
257
正解は… (ここをクリック!)
3の「7」です!
解説 !!
makeAdder(5) を呼ぶと、5 を使う関数が作られます。
このとき、内側の関数は x = 5 の状態を持ったまま外に出てきます。
const add = makeAdder(5);
つまりこの時点で、「5 を足すための関数」ができています。
そのあと、
add(2)
と呼ぶと、makeAdder(5) の時に持っていた 5 と、add(2) の時に渡した 2 を使って、
5 + 2 = 7
となります。
ここでポイントになるのは、関数が作られたときの値(x = 5)が使われているという点です。
つまりクロージャを使うと、
- 値を覚えた関数を作れる
- 同じ関数でも、持っている値を変えられる
ということができるのです!
つまり、
const add = makeAdder(10);
のようにすれば、「10 を足す関数」も作ることができるんです!
このような、関数ごとに違う値を持たせて使える仕組みもクロージャの便利な使い方のひとつなのです 💡
ここまでのポイントおさらい !!
今回の内容をまとめると、次のとおり↓
クロージャ
・関数と、その関数が作られたときの値(スコープ)がセットになったもの
・関数の処理が終わっても、値を持ち続けられる
・状態を覚えた関数を作れる
状態の保持
・カウンターのように値を少しずつ更新できる
・関数ごとに別の値を持たせられる
・同じ関数でも作り方によって動きを分けられる
データの隠蔽
・外から直接触れられない値を持たせられる
・必要な操作だけ公開する形にしやすい
・安全に値を管理したい場面で使いやすい
ループでの注意点
・var は同じ変数を参照しやすい
・非同期処理と組み合わさると意図と違う結果になりやすい
・今のコードでは let を使う場面が多い
クロージャは、関数が作られたときの値をそのまま持ち続けます。
そのため、値を持った関数を作ったり、外から直接触れない状態を作ることができます。
この仕組みによって、JavaScriptで関数ごとに値を持たせたり、外から直接触れない状態を作ることができるのです。
最後に
クロージャは、最初は中身が少し見えにくく感じるかもしれません。
コードの中で、値がどこで作られて、そのあとどう使われているのかを意識して見てみると、自ずと「あ、こういうことか」と感じる瞬間が生まれるはずです!
ぜひ、コードの流れを追いながら値の変化を見てみてください💕
この記事を通して、「あ、これクロージャだ」と気づける場面が増えていってくれると嬉しいです ᥫᩣ ̖́-


コメント