JavaScriptにおけるクロージャは、多くの人が理解に苦しむ概念の1つです。 以下の記事では、クロージャとは何かをわかりやすく説明し、簡単なコード例を使ってポイントを押さえていきます。
クロージャとは、内側の関数が外側(囲んでいる)の関数の変数にアクセスできる、JavaScriptの機能の1つで、スコープチェーンのことです。
クロージャには 3 つのスコープ チェーンがあります:
- 自分自身のスコープ (中括弧の間に定義された変数) にアクセスできます
- 外側の関数の変数にアクセスできます
- グローバル変数にアクセスできます
この定義は、慣れていない人にとっては、ただのたくさんの専門用語のように思えるかもしれません!
でも、実際にクロージャとは何でしょうか?
JavaScriptで簡単なクロージャの例を見てみましょう
function outer() { var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
ここに2つの関数があります。
- 外側の関数
outerbbinner関数を返す - 変数を
ainnerouterbを、その関数本体内でアクセスしています
変数bouterainner 関数に限定されます。
ここで、outer()outer()Xouter()Yに格納してみましょう。
では、outer()の関数が最初に呼び出されたときに何が起こるかを順を追って見ていきましょう。
- 変数
bouter()10に設定されます。 - 次の行は関数宣言なので、実行するものはありません。
- 最後の行では、
return innerinnerinnerinnerを返します。 - return文で返された内容は、
Xに格納されます。
つまり、Xには次のように格納されます:function inner() {
var a=20;
console.log(a+b);
} - 関数
outer()outer()のスコープ内のすべての変数はもはや存在しません。
この最後の部分を理解することが重要です。
関数の中で定義された変数の寿命は、その関数の実行の寿命です。
つまり、console.log(a+b)bouter()という関数が実行されている間だけ存在するということになります。
2回目の実行時には、関数の変数が再び作成され、関数の実行が完了するまでの間だけ存在することになります。
このように、outer()が2回目に起動されたときは、以下のようになります。
- 新しい変数
bouter()10に設定されます。 - 次の行は関数の宣言なので、何も実行しません。
-
return innerinnerに返します。 - return文で返された内容は
Yに格納されます。 - 関数
outer()outer()のスコープ内のすべての変数が存在しなくなりました。
ここで重要なのは、outer()bouter()bは再び存在しなくなります。
ここが最も重要なポイントです。
これが最も重要な点です。関数内の変数は、関数が実行されているときにのみ存在し、関数の実行が完了すると存在しなくなります。
さて、コード例に戻って、XYouter()XYは関数です。
このことは、JavaScriptのコードに以下を追加することで簡単に確認できます。
console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function
変数 XY が関数であることから、実行することができます。 JavaScriptでは、X()Y()()を付けることで、関数を実行することができます。
X()Y()inner 関数を実行していることになります。
ここでは、X()が初めて実行されたときに何が起こるかを順を追って見ていきましょう。
a + bを実行しようとします。 ここで面白いことが起こります。 JavaScriptはaa + bbbbouter()outer()X()outerbも存在しなくなります。JavaScriptではどのように処理しているのでしょうか?
クロージャ
JavaScriptのクロージャにより、innerinner関数は、囲み関数が実行された時点での囲み関数のスコープチェーンを保持しているため、囲み関数の変数にアクセスすることができるのです。
この例では、innerouter()b=10の値を保存しており、引き続き保存(閉鎖)しています。
ここでスコープ チェーンを参照すると、変数 b の値をスコープ チェーン内に持っていることに気づきます。
従って、JavaScriptはa=20b=10a+bを計算することができます。
上記の例に次のコード行を追加することで検証できます。
Google ChromeでInspect要素を開き、Consoleに移動します。 要素を展開して、実際にClosureb=10Closureouter()の中に保持されていることに注目してください。

ここで、最初に見たクロージャの定義を再確認し、より意味のあるものになったかどうかを見てみましょう。
では、内側の関数は3つのスコープ チェーンを持っています。
- 自身のスコープへのアクセス – 変数
a outerb,- 定義されている可能性のあるグローバル変数へのアクセス
クロージャの動作
クロージャのポイントを強調するために、3 行のコードを追加して例を拡張してみましょう。
このコードを実行すると、console.logに次のような出力が表示されます。
var X = outer(); // outer() invoked the first time
関数 outer() が最初に呼び出されます。 以下のような手順で行われます。
- 変数
b10cが作成されます。 -
100
これを参考までにb(first_time)c(first_time)と呼ぶことにしましょう。 -
innerXbinnerb=10innerbを使用しているからです。 -
outercbinner内のクロージャとして存在しています。
var Y= outer(); // outer() invoked the second time
- 変数
b10cが新たに作成されます。
outer()bcが存在しなくなっていたにもかかわらず、関数の実行が完了すると、これらは全く新しい変数として作成されることに注意してください。
これらを参考までにb(second_time)c(second_time)と呼ぶことにします。 innerYに代入されています。bはinnerb(second_time)=10innerbを使用しているからです。-
outer関数の実行が完了し、そのすべての変数が消滅します。
変数c(second_time)b(second_time)inner内のクロージャとして存在しています。
では、次のコード行を実行するとどうなるかを見てみましょう。
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third timeY(); // Y() invoked the first time
X()が初めて起動されたとき、
- 変数
a20 a=20bb(first_time)b=10- 変数
ab1でインクリメントされます。 -
X()aがすべて消滅します。
しかし、b(first_time)b(first_time)は存在し続けます。
X()が2回目に起動されると、
- 変数
aが新たに作成されます。 - 変数
a20X()aの以前の値は存在しません。 a=20b(first_time)b1b=11- 変数
ab1だけ増加します。 再び -
X()が実行を完了し、その内部変数である変数aがすべて消滅します
ただしb(first_time)はクロージャが存在し続けるので保存されます。
X()が3回目に起動されると、
- 変数
aが新たに作成されます。 - 変数
a20X()aの以前の値はもはや存在しません。 a=20bb(first_time)
また、前回の実行でb1b=12- 変数
abb1が再び -
X()aはすべて存在しなくなります
しかし、b(first_time)はクロージャが存在し続けるので保存されます
Y()が初めて起動されたとき。
- 変数
a20に設定されます -
a=20bb(second_time)b=10 - 変数
ab1 -
Y()aはすべて存在しなくなります
ただし、b(second_time)b(second_time)は存在し続けます。
まとめ
クロージャは、JavaScriptの微妙な概念の1つで、最初は理解するのが難しいものです。
これらのステップバイステップの説明が、JavaScriptのクロージャの概念を理解するのに役立つことを願っています。
Other Articles:
- A quick guide to “self invoking” functions in Javascript
- Understanding Function scope vs. Block scope in Javascript
- How to use Promises in JavaScript
- How to build a simple Sprite animation in JavaScript