知らないと怖い「変数の巻き上げ」とは?

2013年05月01日

カテゴリー:

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文を使用して変数を宣言することができます。そして、これらの変数は関数内のいかなる場所で宣言されたとしても、その関数の先頭で宣言されたのと同じように動作します。

JavaScriptでは、関数内で宣言されたローカル変数は、すべてその関数の先頭で宣言されたものとみなされる。

このような振る舞いは「変数の巻き上げ(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パターン」についても、導入しやすく、かつ推奨されているパターンですので、是非実践してみてくださいね。