Posted at

varよりletを使った方が良い

More than 3 years have passed since last update.

と思いました。

たとえば、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