上田です。 このたび「JavaScriptについて何か書いて下さい」という事で、JavaScriptの重要な機能の1つである「クロージャ」について、記事を書かせていただきました。
本稿は、JavaScriptのうちECMAScript Edition 5.1準拠のバージョンを前提として書かれています。
JavaScriptなど、ECMAScript準拠の言語に対応したブラウザのサポートバージョンは、下記URLが参考になります。 http://kangax.github.io/compat-table/es5/ (ECMAScript 5.1のサポート) 補足しますと、上記URLの表のうち「WebKit」とある列は下記URLにあるブラウザが対応しています。 http://bit.ly/1RODveo
JavaScript のグローバル変数
JavaScriptでは基本的に、グローバルに宣言された変数は、外部のどのソースファイルのコードからも参照されます。この仕様は複数のファイルで共通の名前を定義して処理を共有する時など、便利な面も多いです。 しかし逆に、1つのHTMLファイルで複数のJavaScriptファイルを読み込むと、グローバル変数(関数)の名前が衝突するという問題があります。また、不必要に宣言した変数や関数が参照・更新されてしまうので、バグの原因になる場合があります。 例えば、次のコードを見て下さい。
// グローバル変数 var total = 0; // グローバル関数 function add(n) { total += n; document.getElementById("result").value = total; } add(3); add(3); // 6をresultに出す
実行結果 https://jsfiddle.net/cs615rLk/
このコードは、カウンタ変数 totalに関数 addに与えた引数を加算し、テキスト欄に出す事を想定しています。ここでカウンタ変数 totalは参照・変更可能なので、次の様にすると期待する結果になりません。
// グローバル変数 var total = 0; // グローバル関数 function add(n) { total += n; document.getElementById("result").value = total; } total = "foo"; add(3); add(3); // foo33
実行結果 https://jsfiddle.net/cs615rLk/1/
クロージャとは?
この様な外部からの変更を回避するために、クロージャを利用したテクニックがあります。 「クロージャ(Closure)」はJavaScriptの言語機能です。クロージャの特徴は、概要としては次の2行で説明されます。
多くのプログラミング言語では、関数が終了(return)した後、関数内のローカル変数にはアクセス出来なくなります。しかし、JavaScriptでは、そのローカル変数へのアクセスが可能です。
JavaScriptは、変数のスコープが関数(function)単位です。そして、varキーワードを付けて宣言した変数は、それが宣言された関数と、その関数内で宣言された別の関数内でアクセス出来ます。(varを付けない宣言はグローバル変数)
function closureSample(value) { var a = value; function f() { return a } return f; } var s = closureSample("abcde"); s(); // 「"abcde"」が取得される
実行結果 https://repl.it/B15f
ここで得られた”abcde”は、closureSample 関数内の関数 fのreturn 値である変数 aに入っている値となります。 これを使って先程のコードを直してみます。
var adder = (function() { // 外部から実質的にアクセス不可な変数 var total = 0; function _add(n) { total += n; document.getElementById("result").value = total; } // 戻り値として、add関数の呼出しだけを使えるオブジェクトを作る // total変数へのアクセスは提供しない return { add: function(n) { return _add(n); } } })(); total = "foo"; // 内部のtotalは変更不可 adder.add(3); adder.add(3); // totalは変更されず、6がresultに出る
実行結果 http://jsfiddle.net/cs615rLk/3/
クロージャを「関数を定義してすぐ呼び出し」て作っています(これを即時関数と言います)。
この関数は adder変数に入れたここでしか実行されないため、内部に宣言されている total変数にアクセスし変更する手段はありません。これにより、意図しない変数の上書きや、名前空間の汚染(プロパティ上書き)を防ぐ事が出来ます。 但し、ローカル変数への参照が残るとその部分のメモリが解放されないため、ローカルに保持するデータによってはメモリリークの原因となり得ます。その点はご注意下さい。
以上、簡単にですがクロージャの概要と応用例を紹介しました。何かの参考になれば幸いです。 尚、ECMAScriptでは将来的にprivate予約語が追加される予定ですが、実際の機能としてのprivateはまだ先になりそうです。