概要
他の言語同様、javascriptにもクロージャの概念があります。
コードを書きながら理解してみようと思います。
基本的な考え方
クロージャを理解するには、関数とスコープについて整理が必要です。
まず、関数が定義された時、変数の定義がどこでされているかに注意します。
通常、関数を定義すると、スコープチェーンを持ちます。
関数の中に(入れ子の)関数がある状態にある場合、定義された(入れ子の)関数から、親側の関数内で定義された変数を参照できる。という考え方があります。
文章にするとわかりにくいので、まず通常の入れ子の関数を書いてみます。
let test_val = "Global_A"; function test1() { let test_val = "Local_A"; function test2() { return test_val; } return test2(); } console.log(test1()); //出力:Local_A
「console.log(test1());」の行で出力された文字列は「Local_A」になります。
次に、ネストされた関数オブジェクトを返す関数にしてみます。
let test_val = "Global_A"; function test1() { let test_val = "Local_A"; function test2() { return test_val; } return test2; //このタイミングで関数オブジェクトを返す } console.log(test1()()); //出力:Loacl_A
一番外側で「test1()()」という形で(親となる)関数内にあるネストされた関数をオブジェクトを使って呼び出しています。
最初の例と合わせて、どちらも「Local_A」が出力されます。
簡単なクロージャの書き方(例)
ここで、非常に簡単なクロージャの例を書いて動かしてみます。
var test1 = (function() { var counter = 0; return function() { return counter++; }; }()); console.log(test1()); console.log(test1()); console.log(test1()); console.log(test1());
上記のコードを動作させると、出力結果は
0 1 2 3
となります。
test1()を呼び出す度に、関数内部で宣言されているローカル変数counterがカウントアップされた結果が返ってきます。
test1に返ってくる値は全体が括弧()で囲まれているので、内部の無名関数の戻り値が代入されます。
上記の例では、無名関数内に一つの入れ子型の関数がある形になりますが、
無名関数が2つある場合は次のような書き方ができます。
function test2() { var n = 1; return { doubleup: function() { n = n * 2; return n; }, reset: function() { n = 0; } }; } //関数test2を変数へ代入し、オブジェクトを2つ生成する var cnt1 = test2(); var cnt2 = test2(); //出力確認(1回目) console.log("----"); console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup()); console.log("----"); //cnt1の内部カウンタをリセットする cnt1.reset(); //出力確認(2回目) console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt1.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup()); console.log(cnt2.doubleup());
上記のコードの出力結果は次のようになります。
---- 2 4 8 16 2 4 8 16 ---- 0 0 0 0 32 34 128 256
最初の8行では、生成したオブジェクト「cnt1」と「cnt2」に対して、カウントアップ(2ずつ加算)する処理がされて出力されます。
その後、cnt1に対してreset()メソッドを呼んでいるので、
後半の8行では、cnt1についてはまた最初の値からカウントアップされています。
生成したオブジェクトが別な場合は、内部の変数が同じでも、共に影響しない形でアクセスできていることがわかります。
クロージャのシンプルな例として、上記のような関数内への変数の取り扱い方があるので、基本として抑えておくのが良いかと思います。
クロージャにはまだ別な書き方があるので、次回のブログとして取り上げようと思います。