【クイズでわかる】JavaScriptのクロージャとは?値を覚える関数の動きと特徴を理解する

こんにちは!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. 1, 2, 3 と増えていく
  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 に書き換えることはできるでしょうか?

  1. できる
  2. できない
  3. increment() を1回呼んだあとだけできる

正解は… (ここをクリック!)

2の「できない」です!


解説 !!

countcreateCounter() の中で定義されています。

この時点で、外に出ているのは 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秒後に表示される数字はどうなるでしょうか?

  1. 0, 0, 0
  2. 0, 1, 2
  3. 3, 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));

このコードの結果はどうなるでしょうか?

  1. 2
  2. 5
  3. 7

正解は… (ここをクリック!)

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で関数ごとに値を持たせたり、外から直接触れない状態を作ることができるのです。


最後に

クロージャは、最初は中身が少し見えにくく感じるかもしれません。

コードの中で、値がどこで作られて、そのあとどう使われているのかを意識して見てみると、自ずと「あ、こういうことか」と感じる瞬間が生まれるはずです!

ぜひ、コードの流れを追いながら値の変化を見てみてください💕

この記事を通して、「あ、これクロージャだ」と気づける場面が増えていってくれると嬉しいです ᥫᩣ ̖́-

コメント

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