Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

  1. トップページ
  2. JavaScript中級編 - ホイスティング 宣言の巻き上げ

ホイスティング 宣言の巻き上げ

みなさまどうもこんにちは。いかがお過ごしでしょうか。

続きましてJavaScriptの『ホイスティング』についてやっていきましょう。これは『宣言の巻き上げ処理』が行われるという挙動を意味します。JavaScriptにおける闇の部分になりますね。

これまで当サイトでは変数宣言の記述方法などを紹介してきました。以下のような形ですね。

function hoge(){
    var x;
    console.log(x); // 変数『x』は宣言しただけなので『undefined』と出力されます。
}
hoge();

上記は関数『hoge』を定義し、その中でローカル変数『x』を宣言、それをコンソールに出力しています。変数『x』は宣言しただけなのでコンソールには『undefined』と出力されます。ここまでは特に問題ないかと思います。

さて、では変数宣言とコンソール出力を反転させたらどうでしょうか。以下のような感じですね。

function hoge(){
    console.log(x);
    var x;
}
hoge();

基本的にプログラムは上から順番に処理が行われるものです。上記の場合は変数『x』の宣言の前に『x』を出力する、という形になっていますのでエラーになりそうなもんですが、なんとJavaScriptではエラーになりません。ちゃんと『undefined』と出力してくれます。

これはなぜかというとJavaScriptでは『常に宣言はスコープの先頭で行われたことにする』という処理を裏側で行うせいだったりします。JavaScriptでのスコープは関数の中以外では存在しませんので、つまり『常に宣言は関数の中の先頭で行われたことにされる』ということになります。これが『ホイスティング』と呼ばれる現象です。
※スコープについてはこちら

しかもややこしいことに巻き上げられるのは宣言のみ、となりますのでさあ大変です。代入式とかは書いてあるとおりの順番で処理されます。以下のサンプルを確認してみてください。

function hoge(){
    console.log(x); // 巻き上げられるのは宣言のみなので『undefined』と出力されます。
    var x = 0; // ここで代入処理が行われます。
    console.log(x); // 代入処理が行われた後なので『0』が出力されます。
}
hoge();

いかがでしょうか。とても気持ち悪い素敵な機能ですね。

これは他のプログラム言語でもあまりみない挙動になりますので思わぬミスの原因になりがちです。以下のサンプルを確認してみてください。以下は関数『hoge』の最初にグローバル変数『x』の値を出力するつもりでミスをしてしまった例です。

var x = 0; // ここはグローバル変数

function hoge(){
    console.log(x);
    // ここに処理が沢山書いてあるとします。
    // ...
    // ...
    // ...
    // ...
    // ...
    var x = 1;
}
hoge();

察しの良い方はもう想像ついたかと思いますが、上記のサンプルでコンソールに出力されるのは『0』ではなく『undefined』です。

なぜかというと宣言のみが巻き上げられてしまうため、以下のソースコードと同等になるからです。

var x = 0; // ここはグローバル変数

function hoge(){
    var x; // 宣言のみが巻き上げられます。グローバル変数ではなくローカル変数『x』の定義となりますので『undefined』が入ります。
    console.log(x); // ローカル変数『x』の『undefined』が出力されます。
    // ここに処理が沢山書いてあるとします。
    // ...
    // ...
    // ...
    // ...
    // ...
    x = 1; // ローカル変数『x』に『1』が代入されます。
}
hoge();

このように意図せずグローバル変数と同じ名前でローカル変数を定義してしまった際になぜかグローバル変数が取得できない、という展開になってしまいます。JavaScriptの裏側の動きを把握していないとハマってしまうこと請け合いです。

ちなみに変数だけでなく配列やオブジェクトなどでも同じです。

function hoge(){
    console.log(x); // 宣言が巻き上げられ『undefined』と出力されます。
    console.log(y); // 宣言が巻き上げられ『undefined』と出力されます。
    var x = [0, 1]; // ここで代入処理が行われ配列『x』となります。
    var y = { // ここで代入処理が行われオブジェクト『y』となります。
        "hoge": 0,
        "hoge1": 1
    };
    console.log(x); // 代入処理が行われた後なので『[0, 1]』が出力されます。
    console.log(y); // 代入処理が行われた後なので『{"hoge": 0, "hoge1": 1}』が出力されます。
}
hoge();

この巻き上げに関する予期せぬミスを予防する策として、よく使われるのが変数宣言をすべて関数の最初にもっていってしまうという手法です。以下のような感じのソースコードを

function hoge(){
    var x = 0;
    console.log(x);

    var y = 1;
    console.log(y);

    var z = 2;
    console.log(z);
}
hoge();

このように書き直す感じですね。

function hoge(){
    var x = 0,
        y = 1,
        z = 2;

    console.log(x);
    console.log(y);
    console.log(z);
}
hoge();

この手法は行っている方も多く、参考書などでも良く掲載されているパターンになります。

しかし、上から順番に処理が流れていくようなソースコードが好き、という方もいらっしゃいます。

宣言文を全て上にもっていってしまうとソースコードを読み進めるにあたって、変数の内容を確認するために関数の最初に戻って確認したらまた読み進めて〜、という感じで行ったり来たりになってしまいがちになります。なので必要になった時に宣言や代入式が記述してあるほうが読みやすい、と感じる方ですね。私もそうです。

その場合はオブジェクトを文頭で宣言してしまって、そのオブジェクトの中だけで処理を進める、という手法をとると良いかもしれません。以下のような感じです。このようにすれば上から下へ流れるようなソースコードで構築することができます。

function hoge(){
    var elem = {}; // 冒頭でオブジェクトを宣言してしまう。
    elem.x = 0; // 定義したオブジェクトの中だけで処理を進める。
    console.log(elem.x);

    elem.y = 1; // 必要になった時に代入式を記述する。
    console.log(elem.y);

    elem.z = 2; // 必要になった時に代入式を記述する。
    console.log(elem.z);
}
hoge();

肝心なことは『関数の冒頭でローカル変数やローカルオブジェクトを宣言することによって巻き上げに関するミスの防止を図る』ということなので、上記のようなオブジェクトを使用した記述パターンでも先ほどのようなローカル変数を全て先頭に持っていく記述パターンと同じ効果が得られていると思います。ただし、この記述方法はあまり見かけない方法なので一般的な手法ではないかもしれません。そこはご注意ください。

『ホイスティング』に関する解説は以上となりますね。慣れないとややこしい感じがありますが頑張ってください。

続いての記事では『無名関数』について解説していきます。ではまたお会いしましょう。

この記事は桜舞が執筆致しました。

著者が愛する小型哺乳類

桜舞 春人 Sakurama Haruto

ISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。