クロージャ

読めば分かるけど、書けない(涙)状態なので、練習。

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の肝かなと思う。