JavaScript入門 結局クロージャはなんのために使うのか 具体的な例から活用方法を理解する

以下のコードがあります。

let count = 0;

const addCount = () => {
  count++;
}

addCount();
console.log(count); //1
addCount();
console.log(count); //2

addCount を実行すると count 値が1増えるという単純な処理です。

グローバル変数をなくしたい

しかし、count はグローバル変数なので、どこからでも変更できてしまいます。

let count = 0;

const addCount = () => {
  count++;
}

addCount();
console.log(count); //1
addCount();
count = 999 //誰かが書いたコード
console.log(count); //999

このように誰かが count = 999 を書いてしまうと、予期せぬ結果になります。

実際にはこんなに単純ではなく、例えば誰かが新しい関数を追加し、その関数の中で実は count を変更していた、という感じです。

グローバル変数はどこからでも参照できるので便利な反面、どこからでも変更できてしまうので、予期せぬ動作になった際にその原因を追いづらく、バグの温床になりがちです。

そのため、この count をグローバル変数ではなくそうと思います。

関数の中に移動してもうまくいかない

試しに、addCount関数の中に変数 count を入れてみました。

const addCount = () => {
  let count = 0;
  return count++;
}

console.log(addCount()); //0
console.log(addCount()); //0

うまくいきません。

addCount()が実行されると count 変数は消えてなくなります。(変数 count はaddCount関数が実行されているときだけ生存する)

そのため、2回目にaddCount()が実行されると、またあらたな count 変数になってしまうので、意図した動きになりません。

これならどうでしょう。

const addCount = (currentCount = 0) => {
  return currentCount + 1;
}

let c = addCount(0);
console.log(c); //1
c = addCount(c);
console.log(c); //2

これなら想定通りの動きになりますが、変数cはグローバル変数なので解決になっていません。

クロージャをつかう

そこでクロージャを利用します。コードは以下です。

const obj = (() => {
  let count = 0
  return {
    addCount(){
      count++;
    },
    getCount(){
      return count;
    }
  }
})()

obj.addCount();
console.log(obj.getCount()); //1
obj.addCount();
console.log(obj.getCount()); //2

初めて見るとよくわからないことをやっていると思います。しかし、すべてを理解する必要はありません。

順を追ってみていきます。

obj の中身はどうなっているかというと、以下のように、addCountという関数と、getCountという関数が定義されたオブジェクトです。

{
    addCount(){
      count++;
    },
    getCount(){
      return count;
    }
}

これら二つの関数は、変数 count を参照しています。(関数内で count がありますね)

このように変数 count への参照がある場合、addCountやgetCountの実行後も、変数 count は残りつづけます。

ただし、変数countに直接アクセスすることはできません。

const obj = (() => {
  let count = 0
  return {
    addCount(){
      count++;
    },
    getCount(){
      return count;
    }
  }
})()

console.log(obj)

obj.addCount();
console.log(obj.getCount()); //1
obj.addCount();
console.log(obj.getCount()); //2

obj.count = 999
console.log(obj.getCount()); //2のままになる。つまりobj.countと変数countは別もの

obj.count = 999 としていますが、それでもobj.getCount()の結果は2のままです。

外から変数countに対して直接アクセスできないため、グローバル変数であった問題は解消されています。

また、変数countは残り続けるため、countの値がリセットされる問題も解消されています。

まとめ

クロージャの仕組みを使うと以下のような問題を解消できます。

  • グローバル変数はできるだけなくしたいな
  • でも、変数を関数の中においてしまうと、関数が実行されるごとに毎回リセットされてしまうので、永続化できないな

今回のcountのように、アプリが起動中は、そのデータを永続化したいケースがあると思います。

その場合、グローバル変数を使うのが普通です。

なぜなら、関数の中に変数を定義してしまうと、関数の実行ごとにリセットされるので、永続化できないからです。

しかし、グローバル変数は予期せぬ動作を生みやすいのでできるだけ使いたくない。

この二つの要求を満たすために、クロージャの仕組みを使います。

クロージャの動作は難しいので、はじめは理解が難しいと思います。すべてを理解する必要はないです。

ただ、クロージャの仕組みを利用すると、上記のような問題を解消できることだけでも覚えておくと良いと思います。