クロージャ
読めば分かるけど、書けない(涙)状態なので、練習。
Hello Closure!
var count = function() { var i = 0; return function() { return ++i; }; }(); alert(count()); // 1 alert(count()); // 2 alert(count()); // 3
ちなみにperlだとこんな感じ。
my $closure; { my $i = 0; $closure = sub { return ++$i; }; } print $closure->(), "\n"; // 1 print $closure->(), "\n"; // 2 print $closure->(), "\n"; // 3
さて、これだけだと全く面白くないので、もう少し頑張る。テキストボックスに入れた会社名を証券コードに変換して出力する処理を書いてみる。
<html> <head> <script type="text/javascript"> var stockCode = function() { var map = { gree: 3632, dena: 2432, yahoo: 2432, mixi: 2121, ca: 4751, }; return function (node, outId) { var companyName = node.value; document.getElementById(outId).innerHTML = map[companyName.toLowerCase()]; }; }(); </script> <body> <form> <input type="text" onblur="stockCode(this,'code');"/> <span id="code"></span> </form> </body> </html>
クロージャ使うことで変換テーブルを隠蔽でき、かつ連想配列の生成を1回にすることができる。
そもそもクロージャって何だ?
Wikipediaを調べてみた。
クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。
(中略)
クロージャは典型的には関数コードへのポインタ及び関数の作成時の環境の表現(例えば、使用可能な変数とその値の集合など)を含む特別なデータ構造によって実装される。
ふむー。
いくつか調べたところによると、エンクロージャのローカル変数を参照していなくても内部関数を生成してしまえば、クロージャになるらしい。JavaScriptの場合。深追いしてないけども。
練習
Life is beautiful: Javascriptクイズ(中級者向け):無名関数と実行効率の話のお題をやってみる。
var style2prop = function () { var regex = /\-[a-z]/g; return function (str) { return str.replace(regex, function (str) { return str.charAt(1).toUpperCase(); }); }; }(); var style = 'background-color'; alert(style2prop(style));
という風につくってみて、模範解答と照らしてみるとreplaceの第2引数の無名関数を毎回作成してる気がする。改めて書き直し。
var style2prop = function () { var regex = /\-[a-z]/g; var capitalize = function(str) { return str.charAt(1).toUpperCase(); }; return function (str) { return str.replace(regex, capitalize); }; }(); alert(style2prop('background-color'));
練習その2
さらにLife is beautifulのお題をやってみる。
「そうだ、クロージャへの理解を深めるために一つ宿題をあげよう」と続ける三郎。「今の、"font-style"を"fontStyle"に変更する関数style2propに加えて、"font_style"を"fontStyle"に変更する別の関数hoge2propを同じライブラリの中に作りたいとき、その二つの関数でcapitalizeを共有しつつ、かつそれをクロージャを使って隠蔽するにはどうしたら良いか考えてみると良いよ。」と言って立ち去ってしまう三郎。
ということで、今回の宿題は、一つのプライベートな関数を複数のパブリックな関数で共有しつつ、そのプライベート関数をクロージャを使って隠蔽するテクニック。さあ、太郎はどんな答えにたどりついたでしょう?
var style2prop, hoge2prop; (function () { var capitalize = function (str) { return str.charAt(1).toUpperCase(); }; style2prop = function (str) { return str.replace(/\-[a-z]/g, capitalize); }; hoge2prop = function (str) { return str.replace(/_[a-z]/g, capitalize); }; })(); alert( style2prop('background-color') ); alert( hoge2prop('background_color') );
クロージャの注意点
DOMオブジェクトとscriptオブジェクトで循環参照しちゃうとか、注意したほうがいいらしい。
DOM オブジェクトとメモリリーク: Days on the Moon
まとめ
クロージャの勉強を通して、JavaScriptについてだいぶ理解。
関数オブジェクトに加え、プロトタイプやthisについてきちんと理解しておくことがJavaScriptの肝かなと思う。