スポンサーリンク

JavaScriptの動かないコード (中級編) クロージャを使わない場合に起きるエラー


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:
 変数の値を1増やして,表示する。

<input type="button" value="「1」と表示" onClick="f1()" >
<input type="button" value="「2」と表示" onClick="f2()" >


<script language="JavaScript">

function f1(){
	var i = 0;
	
	// i の値を 1 増やす関数を, f1 の中に作成
	var g = function()
	{
		i++;
	}

	// i の値を 1 増やす
	g();
	alert( i );

	// 今後 f1 中で i の値を増やしたい場合は,
	// 毎回 g() を呼び出せばよい。
}


function f2(){
	var j = 1;

	// j の値を 1 増やす関数を, f2 の中に作成
	var g = plusOne;

	// j の値を 1 増やす
	g();
	alert( j );

	// 今後 f2 中で j の値を増やしたい場合は,
	// 毎回 g() を呼び出せばよい。
}


function plusOne()
{
	j++;
}

</script>




答え



「1と表示」のほうはうまくいくが,「2と表示」の方は何も表示されない。

// j の値を 1 増やす
g();

に差し掛かった時点で,「変数 j が定義されていない」というエラーになる。


f1 の場合でも f2 の場合でも,その関数の内部にある「 g 」というものが値を1増やす処理を担っている点は同じのはず。

g はそれぞれの関数の内部にあるのだから,f1 内部の g が i を参照できているように,f2 内部の g も j を参照できるはず。

このように考えるが,しかし f2 のほうではそうはならない。



理由は,クロージャの性質にある。


クロージャとは,「『関数が定義された環境』への参照を持っている関数」のこと。一般には,「関数内で定義された内部関数」のことを指す場合もある。*1


mozillaのwebサイトに,クロージャの説明と,使い方と,よくある間違いが載っている。

Working with Closures
http://developer.mozilla.org/Ja/Core_...


JavaScriptでは,関数定義はクロージャになる。(もしくは,「JavaScriptにはスコープチェインがある」という言い方をする。)
下記サイトなどを参照のこと。

 http://builder.japan.zdnet.com/sp/javascript-kickstart-2007/story/0,3800083428,20378258,00.htm


クロージャの内部で変数を使う場合,

  • まずクロージャ内部でその変数が定義されているかどうか探す。
  • 次に,クロージャが定義された環境で,その変数が定義されているかどうか探す。


冒頭の f1 の場合,クロージャ g が定義された環境とは, f1 の中であり,そこで i が定義されている。
なのでクロージャ g の内部からは f1 中の i を参照できる。


f2 の場合, g の中身は関数 plusOne である。

plusOne が j を探しに行く場所は,f2 の中ではなく,plusOne の外のスコープ(グローバルの文脈)である。
なぜなら plusOne は f2 の中で定義されているのではないから。

j という名前はグローバルで定義されていないので,plusOne は j を見つけられない。よって実行時にエラーになる。


関数を「使う」環境ではなくて,あくまで,関数が「定義」された環境を参照するのがポイント。



上のMozillaのサイトには,典型的な間違いとして「forループ内でクロージャを作成しなかった場合」というケースが載っている。
ループの各回の実行ごとに異なる値を割り当てたつもりになってしまうが,実際には一定値を参照してしまうというもの。

このMozillaでの説明よりもさらにシンプルな説明が下記のサイトにある。

JavaScriptでのクロージャの使い方
http://blogs.wankuma.com/kacchan6/arc...

 

*1:クロージャの特徴は,「関数の定義された外部環境が破棄されないで生き続ける」こと。しかしこの記事ではその性質を明示的に使わない。