僕らは JavaScript を知らない - 巻き上げ Hoisting

参考:

JavaScript において、変数を宣言するには varletconst を利用します。これらのうち、 var let では、初期値を指定せずに宣言した場合すべて undefined になります。

var a;
let b;

console.log(a); // undefined
console.log(b); // undefined

変数や関数の宣言が、自身のスコープ内のコードの一番上に来ているかのような動作を巻き上げ (Hoisting) と呼びます。例えば、次のコードを実行してみましょう。

a = 1;
var a;

console.log(a);

上記のコードを上から順に解釈していけば、 undefined と出力されそうですが、実際には 1 が出力されます。関数宣言でも同様です:

foo();

function foo() {
    console.log(1);
}

foo 関数の宣言前に foo を実行しているようにみえますが、出力は 1 になります。コードの実行順に即したコードはそれぞれ次のようになります:

var a;
a = 1;

console.log(a);
function foo() {
    console.log(1);
}

foo();

このように、変数や関数の宣言をスコープ内の一番上 (今回はグローバルスコープ) にもってくることを巻き上げ (Hoisting) と呼びます。実際の JavaScript の動作としては、コード実行前のコンパイル時に宣言式を処理しているというだけで、宣言式を物理的に動かしているということはありません。

注意点として関数式は巻き上げされないため、次のコードではエラーが発生します:

foo(); // Uncaught TypeError: foo is not a function

var foo = function() {
    console.log(1);
}

変数 foo の宣言は巻き上げられて処理されます。しかし、関数の代入はコードの実行時に処理されるため、 foo()undefined を実行し、 TypeError が発生します。コードの実行順に即して書き直すと次のようになるでしょう:

var foo;

foo();

foo = function() {
    console.log(1);
}

foo の値が undefined になることがよく理解できるのではないでしょうか。

また、 let / const では巻き上げは起こりません。

console.log(a); // ReferenceError: a is not defined

let a = 1;