[JS]クロージャをざっくり理解する

本記事ではJavascriptのクロージャについて説明を行います。

クロージャとは

クロージャ(関数閉包)はグローバルの汚染を防ぎ、関数が状態を保持出来る仕組みです。

あたかもグローバルに定義した変数をその関数が扱うかのように振る舞いますが、レキシカルスコープという特殊なスコープにある領域にある変数を扱うため、その変数は外部から参照出来なくなっています。

クロージャの構造

文章で説明するよりもコードを見た方が早いと思います

const outerFunc = () => {
  let name = '田中' // 外部関数で宣言したローカル変数

  // 内部の関数
  const innerFunc = () => {
    console.log(name) // 外部関数のローカル変数を使う
  }

  return innerFunc // 内部の関数を戻り値として返す
}

const fn = outerFunc // outerFuncを呼び出してinnerFuncを返す
fn() // innerFuncの外側で定義したnameの値(田中)が表示される!!

まず、outerFuncは関数を返す関数になり、内部で定義したinnerFuncを返します。

返されたinnerFuncの外側で宣言したローカル変数(outerFunc側から見て)の値を参照する事が出来ます。

初見では返されたinnerFuncを実行しても変数nameをinnerFunc内で宣言していないので、ReferenceErrorがスローされると思っていたのですが、Javascriptではレキシカルスコープと呼ばれるスコープがあり、関数の中で定義した関数の中で親関数のローカル変数の値をメモリ上に保持する特徴を持っています。

「保持する」ということは、言い方を変えるとクラスのインスタンス変数のような使い方を出来るので、グローバル汚染を防ぐことにつながります。

次にもう少し踏み込んだ使い方を紹介してみます。

カウンターをクロージャを使って実装する

呼び出すたびに1ずつインクリメントされた数字を返す関数をクロージャを使って実装してみます。

const counterFactory = () => {
  let count = 0
  const counter = () => {
    return ++count
  }

  return counter
}

const counter1 = counterFactory()
console.log(counter1()) // 1
console.log(counter1()) // 2
console.log(counter1()) // 3

const counter2 = counterFactory()
console.log(counter2()) // 1
console.log(counter2()) // 2
console.log(counter2()) // 3

counterFactoryは先ほどの例と同じく内部関数counterを返す外部関数です。

返された内部関数counterも先ほどと同様、レキシカルスコープ変数であるcountの値を保持する事が可能です。

ですので、内部関数を呼び出すたびにcountの値がインクリメントされてそれが戻り値として返されます。

また、counterFactory関数を再度実行してcounter2変数に内部関数counterを代入しました。

この場合、最初のcounter1とは別のメモリ領域にレキシカルスコープ変数countが格納されるため、counter2()を実行しても最初に返される値は1からになります(counter1とは独立しています)。

最後に

クロージャについてはもっと説明しないといけない部分がまだあるのですが、今回は自分への覚え書き程度に簡単に概念を説明してみました。

使い所については今後の記事で紹介できればと思っています。

それじゃ、また