JavaScriptには、他の言語ではあまり聞かない(あるいは存在しない)「変数の巻き上げ(hoisting)」という概念があります。これは(たぶん)JavaScript特有のもので、かつ重要なポイントです。
この「変数の巻き上げ」が原因でコードが思った通りの動作をしなかった場合、この概念を知らないと、いくらコードを見直しても問題を発見することができません。ドツボにはまります。
そういう意味でも、この「変数の巻き上げ」の概念をおさえておくことは重要です。
とりあえず問題
変数の巻き上げを説明するには、文章で説明するよりもコードを見てもらった方が理解できると思います。
まずは次のコードを見てください。
var myname = "global"; function func() { console.log(myname); //出力内容は? var myname = "local"; console.log(myname); //出力内容は? } func();
上記のコードの4行目と6行目の出力内容はどうなるでしょうか?
おそらく、最初のconsole.logが”global”、そして次のconsole.logが”local”と思った方も多いと思います。これはもっともな予想ですね。他の言語の概念からすればそれが正解になると思います。
ですが、JavaScriptでは違います。
正解は最初のconsole.logが”undefined“、次のconsole.logが”local“になります。不思議ですね。
普通だったら、最初のconsole.logの時点では、その下にある var myname = “local”; は宣言されていないため、グローバルスコープの myname つまり、”global” が出力される、と考えてしまいます。
ではなぜそうならなかったのでしょうか?
なぜこうなるのか?
JavaScriptでは、関数内のどこにでもvar文を使用して変数を宣言することができます。そして、これらの変数は関数内のいかなる場所で宣言されたとしても、その関数の先頭で宣言されたのと同じように動作します。
このような振る舞いは「変数の巻き上げ(hoisting)」と呼ばれます。
先ほどのコード例は、実際には以下のコードと同じ振る舞いとなります。
var myname = "global"; function func() { var myname; console.log(myname); myname = "local"; console.log(myname); }
最初のコード例の5行目の変数宣言
var myname = "local";
が巻き上げられ、関数の先頭で宣言されたように扱われます。ただし、巻き上げられるのは宣言部分のみなので、この時点ではその中身(= “local”)の代入までは行われません。つまり関数の先頭部分は
var myname;
となり、これは
var myname = undefined;
と同じ意味ですので、最初のconsole.logでは「undefined」が出力された、ということです。
そして、次の行では実際に変数 myname に対し “local” が代入されるので、2番目のconsole.logでは “local” が出力されます。
まとめと予防策
変数の巻き上げはJavaScript特有の概念です。といっても、この「変数の巻き上げ」をコーディング中に常に意識しておく必要があるかというと、実はそんなことはありません。要は変数の巻き上げが起きないようなコードを書くようにすればよいのです。
方法はいたって簡単で、関数の中で使用されるすべてのローカル変数を関数の先頭で宣言するだけです。
こうすれば、関数内で「変数の巻き上げ」は起こり得ません。また、変数を関数の先頭で宣言することは、コードの見通しを良くすることにも役立ちます。
関数内で使用するローカル変数が多く、いくつもの var を書かなくてはならない場合は、JavaScriptにおけるコーディングパターンの一つである「単独varパターン」を使用することにより、よりすっきりとコーディングを行うことが可能になります。(単独varパターンについては、こちらで詳しく紹介しています。)
「変数の巻き上げ」にメリットなどありません。また、予期せぬ巻き上げは意図しない結果をもたらす場合もあるため、JavaScriptコーディングの際はこの「巻き上げ」が起こらないようなコーディングをすべきだと思います。
それには、「関数の先頭ですべてのローカル変数を宣言する」というだけのとても簡単でシンプルな解決方法があります。また、これに関連したコーディングパターンである「単独varパターン」についても、導入しやすく、かつ推奨されているパターンですので、是非実践してみてくださいね。