これでできる! クロスブラウザJavaScript入門

第5回 JavaScriptの基礎知識#2:クロージャ編

この記事を読むのに必要な時間:およそ 3.5 分

こんにちは,太田です。前回はJavaScriptの基礎的な部分を解説しました。今回はJavaScriptのクロージャについて解説します。クロージャはJavaScriptでは使用頻度が高く(意識して使用していなくとも,ほとんどの場合クロージャが使われています)⁠今後の連載の中でも積極的に使っていきますのでここで確実に理解してしまいましょう。

クロージャとは

クロージャはその定義を説明されてもなかなか理解できないため,難しいものだと思われがちです。しかし,ソースコードを中心に見方を少し工夫すればすんなりと理解できると思います。

さて,クロージャの前に確認しておくべき基本事項があります。それは,JavaScriptでは関数を入れ子にできる,という点です。ある関数の中に別の関数を定義することができます。基本中の基本ですが,これがクロージャにおいてもっとも重要です。

では,それを踏まえて次のコードを見てください(変数名・関数名に日本語を使っていますが,文字コードにさえ気をつければこのままでも動作します)⁠

関数の中の関数

function 関数A(){ var 変数a = 'a';
function 関数B(){ var 変数b = 変数a + 'b';
var 関数C = function(){ var 変数c = 変数a + 変数b + 'c'; alert(変数c); // aabc }
関数C(); }
関数B(); }
関数A();

関数Aの中に関数Bを,さらにその中に関数Cを,と関数の定義を入れ子にしてみました。ここで,関数Aのローカル変数である変数aを関数Bと関数Cの内部でも使用しています。

関数Bは関数Aの中に定義された関数(内部関数)なので,同じく関数Aの中で定義された変数aを関数Bの中からも参照することができます。ここまでは「見た目の通り」なので何も複雑なことはないと思います。

さてさて,実はこれが既にクロージャの実例となっています。このように関数の中に定義した内部関数が外側の関数(エンクロージャ)のローカル変数を参照できる仕組みをクロージャと呼んでいます(正確にはもっと小難しい説明になるのですが,JavaScriptではそれだけのことです)⁠なお内部関数Bから見た変数aはローカル変数でもグローバル変数でもないので,これをレキシカル変数と呼びます。

このように,JavaScriptでは関数を定義した段階でその関数から見える変数が決まっています。それゆえに,関数の中に関数があるとき,内部の関数は外側にある変数を参照できるという見た目の通りの動作をします。

クロージャの生成と変数

function 関数A(){ var 変数a = 0;
function 関数B(){ 変数a++; }
関数B(); 関数B(); alert(変数a); // 2 }
関数A(); 関数A();

こちらは関数Aを2度呼び出しています。一度目の呼び出しでは2がalertされます。また,2度目の呼び出しでもやはり2がalertされます。関数Bは何度呼び出しても同じ変数aをインクリメントするので,変数aはどんどん値が増えていきますが,一方で関数Aが呼び出されると変数aは初期化されています。当たり前だと思われるかもしれませんが,このことを踏まえて次のソースコードを見てください。

クロージャと高階関数

function 関数A(){ var 変数a = 0;
function 関数B(){ return ++変数a; }
return 関数B; }
var 変数β = 関数A(); alert(変数β()); // 1 alert(変数β()); // 2 var 変数γ = 関数A(); alert(変数γ()); // 1 alert(変数β()); // 3

こちらはクロージャと前回取り上げた高階関数との組み合わせです。関数Aは関数Bを返すようにしたので,それを変数βに入れました。変数βはもちろん関数Bなので,変数βを呼び出すと関数Bの定義通り変数aをインクリメントしてからその値を返します。変数β自体は関数Aと関係がありませんが,中身の関数Bは関数Aの内部関数なので,どこからどのように呼び出したとしても関数Bが変数aを参照できることは変わりません。繰り返しますが「関数を定義した段階で」参照できる変数が決まるからです。そのため,変数βを関数として呼び出すたびに変数aがインクリメントされていきます。

一方で,関数Aを再び呼びだして新しい変数γに代入すると,やはり変数γも変数aを0から順番にインクリメントした結果を返します。この結果のとおり,変数βが内部に持つ変数aと変数γが持つ変数aは別物であることがわかると思います。

このあたりは少々ややこしくなってきましたね。とはいえ,関数Aが呼び出されたときに変数aは初期化されるので,関数Aが10回呼び出されたときは変数aも10回初期化されてそれぞれが独立しているのは当然なので,関数Bから参照できる変数aもそれぞれが独立しているのは当然のことです。

ここまででクロージャの解説はほぼ終わりです。ここからは実用的なケースを見ていきます。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/

コメント

  • 疑問点

    良い記事をありがとうございます。
    勉強させていただいております。
    疑問点があったので、コメントさせていただきました。

    ----------------------
    変数がループの数だけ存在するかのように間違いやすいですが,あくまでtrはonload関数のローカル変数でしかありません。つまり,onload内に変数trはただ1つしか存在しません。必然的にonmouseover,onmouseoutから参照する変数trもその1つだけです。
    --------------
    上記の失敗例の箇所は、最後のtrを参照している状態なのに、
    何故マウスオーバー、マウスアウトが、最後のtr以外にも適用されるのでしょうか?CSSが最後のtrにのみ適用されるのならば、マウスオーバー、マウスアウトも同じく最後のtrにのみ適用されるのではないのでしょうか?

    Commented : #1  yoshida (2012/12/20, 10:33)

コメントの記入