と思いました。
たとえば、0から4までの整数を返す5つの関数を作成しようと思って次のようなコードを作成したとしましょう。これを実行すると、期待に反して、arrayのそれぞれの要素を呼び出して得られる値は全て5となります。
function F() {
... var array = [];
... for (var i = 0; i < 5; i++) {
..... array[i] = function () {
....... return i;
....... };
..... }
... return array;
... }
undefined
> var array = F();
undefined
> array[0]();
5
> array[1]();
5
> array[2]();
5
> array[3]();
5
> array[4]();
5
これ自体はJavaScript以外の言語でもやってしまいがちな間違いです。修正しましょう。
> function F() {
... var array = [];
... for (var i = 0; i < 5; i++) {
..... var j = i;
..... array[j] = function () {
....... return j;
....... };
..... }
... return array;
... }
undefined
> var array = F();
undefined
> array[0]();
4
> array[1]();
4
> array[2]();
4
> array[3]();
4
> array[4]();
4
JavaScript以外の普通の言語の感覚で修正しましたが、これでも上手く行きません。C#などならこれで上手く行くはずなのですが。
これは、JavaScriptの場合、変数の有効範囲は関数単位だからです。上のコードの場合、変数j
の有効範囲は関数F
の全域です。ブロック単位の有効範囲というものはvar
で宣言された変数にはなく、5回のループの中で使われている変数j
は全て同じ変数です。
このような場合にはlet
で変数を宣言しなければなりません。let
で宣言された変数はブロック単位の有効範囲を有します。
> function F() {
... var array = [];
... for (var i = 0; i < 5; i++) {
..... let j = i;
..... array[j] = function () {
....... return j;
....... };
..... }
... return array;
... }
undefined
> var array = F();
undefined
> array[0]();
0
> array[1]();
1
> array[2]();
2
> array[3]();
3
> array[4]();
4
ちなみに、let
を使いたくない場合には、クロージャを使います。
> function F() {
... var array = [];
... for (var i = 0; i < 5; i++) {
..... array[i] = (function (x) {
....... return function () {
......... return x;
......... };
....... }(i));
..... }
... return array;
... }
undefined
> var array = F();
undefined
> array[0]();
0
> array[1]();
1
> array[2]();
2
> array[3]();
3
> array[4]();
4