2012年06月11日に投稿

[Javascript] for 構文内で関数リテラルを使用した時のスコープについての注意点

Javascriptでは関数リテラルを変数に代入することで、その変数を関数として実行することができます。とても興味深い仕組みですが、理解するのにも時間がかかりました。特にfor構文内で関数リテラルを使って処理を定義した場合に、想定と全く違った動作をしてしまうことがありました。

具体的には下記のようなコードです。

このコードを最初に見た時、私の頭の中では 1 が表示されるとばかり思っていました。for構文で使っている変数がカウントアップされながら、定義されるというイメージです。つまりは下記と同様の処理が行われると思い込んでいました。

しかしながら、現実は違っていてfor構文を使うとループが完了したあとの 3 が表示されるのです。これはなぜでしょうか。

インターネット上の記事を調べまわり、私のCeleronシングルコア並の処理能力しかない脳みそで考えた結果、「スコープ」がポイントであることにたどり着きました。

関数リテラルで関数を定義すると、記述した場所のスコープで定義されます。function () { return x; } が記述されたときには、この関数内に x はありませんので、一つ外側のスコープを探しに行きます。for構文内はスコープではありませんので、結果的に「関数が実行されたらグローバルスコープの x を参照しにいく」という動作がf[0]、f[1]、f[2]に定義されます。(動作は全部同じ!)これらの関数の実行時のグローバルスコープにある x はfor構文でカウンターとして使い終わった変数ですから、f[0]、f[1]、f[2]どれを実行しても 3 が返されることになります。

では、次の疑問として function () { return x; } のなかの x を、for構文で使っている x を参照するようにしたい場合はどうしたらいいのでしょうか。

即時関数でスコープを作る

即時関数を使うとスコープを作ることができます。その即時間数に引数として、カウンターで使われている x を渡すことで、f[x] = function () { return x; }; で使われている一つ外側のスコープに、カウンターで使われている x が存在することになります。したがって、f[x]()の実行時には x が 0、1、2 のスコープをそれぞれ参照しにいきますので、期待通りの結果を得ることができます。

with構文でスコープを作る

with構文を使うことで即時関数を使った場合と同じようなことができます。with({x:x}){ … } とすることで、withの外側の x をwithの内側の x として使うことができるスコープを作り出すことができるようになります。外側の x はfor構文によってカウントアップされていますから、with({x:0}){ … }、with({x:1}){ … }、with({x:2}){ … }の3回スコープが作成されることになります。実行時には f[x] = function () { return x; }; の定義時に一つ外側で使われているスコープの x を参照しにいくことになるため、即時関数のときと同様の結果を得ることができます。

関連記事

Leave a Reply