読者です 読者をやめる 読者になる 読者になる

クロージャの活用

Web/JavaScript 講習会 part.7 (this, スコープと環境、クロージャ) — ディノオープンラボラトリ」を見て参考になったところをメモ。


今回は、「スコープチェーンと実行時の環境を理解すればクロージャの理解も簡単だよね」っていうようなお話だったと思う。個人的にはかなり内容が濃かったのでまた見直すかも。


スライドに出てきた例題は、クマーのセリフがクマーをクリックすると変化するというもの。具体的には、index.html のクマーの各パーツ(目、鼻、口など)に spanタグで id属性をふって、template.js でその id属性に対応したセリフを表示させるような処理をしている。スライドでは、スコープチェーンを理解するためにわざと失敗したバージョンと成功したバージョンのスクリプトと対比させて説明をしてくれたので分かり易かった。以下に index.html template.js(失敗バージョン) template.js(成功バージョン)をメモっておく。

index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <link rel="stylesheet" href="style.css" type="text/css" media="screen" charset="utf-8" />
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                <script type="text/javascript" charset="utf-8" src="slide.js"></script>
                <title>Web/JS part.7</title>
        </head>
        <body>
            <div id="kuma">
                   <span id="year">∩_____∩</span><br />
                   <span id="head">| ノ      ヽ</span><br />
                  <span id="eyes">/  ●   ● | </span><span id="shout">クマ──!!</span><br />
                  <span id="nose">|    ( _●_)  </span><span id="hige"></span><br />
                 <span id="mouth">彡、     |∪|  、`\</span><br />
                <span id="neck">/ __   ヽノ /´>  )</span><br />
                <span id="body">(___)    / (_/</span><br />
            </div>
        </body>
</html>
slide.js(失敗バージョン)
function curry() {
        // セリフ
        var stuff = {
                eyes: "つぶらな瞳にめろめろ", hige: "それポッキー",
                nose: "それは最終兵器の発射ボタン",
                body: "ぐふぅ・・いいボディだ", mouth: "今日もカレー"
        };
        // セリフを叫ぶ関数を返す
        function getShout(item) {
                return function() {
                        if (item) {
                                document.getElementById('shout').innerHTML = item + "クマー";
                        }
                };
        };
        // クマーの構成パーツ(span) onclick イベントを定義
        var parts = document.getElementById('kuma').getElementsByTagName('span');
        for (var i = 0; i < parts.length; i++) {
                // クリックされたクマーの部位
                var item = parts[i];
                // セリフを叫ぶ関数(ここがまずい。関数(リスナー)から参照する変数 shout をグローバルスコープに置いてしまっている)
                var shout = getShout(stuff[item.getAttribute('id')]);
                item.onclick = function() {
                        console.log("clicked");
                        shout();
                };
        }
}
window.onload = curry;


成功バージョンは「var shout = getShout(stuff[item.getAttribute('id')]);」の後の処理が違う。shout をそれぞれの関数オブジェクトの中に入れて、実行時のスコープチェーンでたどれるようにしている。JavaScript では入れ子型の関数をこのように使うときに限って、クロージャという言葉をつかうらしい。


サイ本の p143 によると、クロージャっていうのはもともとコンピュータサイエンスの言葉で、コードとスコープが対になったものを指すっていうことだから、もしかしたら実装する言語によって違う顔を見せてくれるのかもしれない(ただの想像だけど…)。そういえば、ピッケル本にも「クロージャ」って言葉が出てきた気がする。

slide.js(成功バージョン)
function curry() {
        // セリフ
        var stuff = {
                eyes: "つぶらな瞳にめろめろ", hige: "それポッキー",
                nose: "それは最終兵器の発射ボタン",
                body: "ぐふぅ・・いいボディだ", mouth: "今日もカレー"
        };
        // セリフを叫ぶ関数を返す
        function getShout(item) {
                return function() {
                        if (item) {
                                document.getElementById('shout').innerHTML = item + "クマー";
                        }
                };
        };
        // クマーの構成パーツ(span) onclick イベントを定義
        var parts = document.getElementById('kuma').getElementsByTagName('span');
        for (var i = 0; i < parts.length; i++) {
                // クリックされたクマーの部位
                var item = parts[i];
                // セリフを叫ぶ関数
                var shout = getShout(stuff[item.getAttribute('id')]);
                item.onclick = function(shout) {
                        return function() {
                                console.log("clicked");
                                shout();
                        };
                }(shout);
        }
}
window.onload = curry;