JavaScriptのホイスティング(Hoisting)について

2021年1月17日JavaScript

JavaScriptのホイスティングについてまとめました。

JavaScriptのホイスティング(Hoisting)=宣言の巻き上げ

ホイスティング(Hoisting)とは、コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置することです。
ホイスティングのことを「宣言の巻き上げ」といったりもします。
変数が宣言される前に、その変数をアクセス可能にしたり、利用可能にしたりすることです。
コード実行前に、変数宣言にコードスキャンします。
そして、それぞれの変数のために、変数環境のオブジェクトの中で新しいプロパティを作ります。


a();
function a() {
  console.log('a');
}

上の例のように関数の前で呼び出しても実行できるのは、コード実行の前に関数がすでにメモリ上に存在するためであり、つまりホイスティングがあるためです。

ホイスティングはコンテキストが生成されるたびに起こります。

ホイスティングと初期値とスコープの関係

ホイスティングあり? 初期値は? スコープは?
関数宣言(function declaration) あり 関数(Actual function) ブロックスコープ(strict modeの場合のみ)
var あり undefined(JavaScriptの有名なバグともいわれる) 関数スコープ
let / const なし(厳密にいうとありだけど実際エラーにはなる) uninitialized(Temporal Dead Zone, TDZ、一時的デッドゾーン) ブロックスコープ
関数式とアロー関数(function expressions and arrows) varかlet/constと同じ動き varかlet/constと同じ動き varかlet/constと同じ動き

関数宣言(function declaration)はfunction func1(n) {}という形。
関数式はconst func2 = function (n) {}という形。
varをJavaScirptエンジンが見つけたら、スペースを確保してundefinedをメモリにセットします。
let / constでのホイスティングは、行われているものの、宣言の前に使おうとしても使えません。それはTDZという領域ができていて、初期値を設定してからでないと使えません。

Temporal Dead Zone, TDZ、一時的デッドゾーンについて

次のコードをみてみましょう。


const name = 'yamada';
if(name === 'yamada') {
consol.log(`yamada's age is ${age}`);
const job = 'engineer';
console.log(job);
const age = 40;
console.log(y);
}

上のコードの場合、ageはifのブロックスコープ内にありますが、その宣言される前の部分はすべてTemporal Dead Zone(TDZ、一時的デッドゾーン)となります。
初期化が必要ですといったエラーが返ってきます。
ReferenceError: Cannot access 'age’ before initialization
これはホイスティングはしているので変数ageがあることがわかってはいるものの、初期値を設定する前には使えませんよということです。

本当に宣言もしていないたとえば変数yに関しては次のように定義されていませんというエラーが返ってきます。
ReferenceError: y is not defined

このTDZはより簡単にエラーを発見するためにES6から導入されました。
このエラーのおかげでとても発見しにくいバグを防ぐことができます。

ホイスティングの挙動の具体例


//変数とホイスティング
console.log(yamada); //undefinedが返ってくる
console.log(job); //TDZにあるため、初期化されていないというエラーが返ってくる。
console.log(year); //TDZにあるため、初期化されていないというエラーが返ってくる。

var yamada = 'yamada';
let job = 'teacher';
const year = 2000;

//関数とホイスティング

console.log(addFcDecl(2,3));//正常に答えが返ってくる。
console.log(addFcExpr(2,3));//TDZにあるため、初期化されていないというエラーが返ってくる。
console.log(addArrow(2,3));//TDZにあるため、初期化されていないというエラーが返ってくる。

function addFcDecl(a, b) {
  return a + b;
}

const addFcExpr = function(a, b) {
  return a + b;
}

const addArrow = (a, b) => a + b;
var addArrow = (a, b) => a + b; // is not a functionというエラーが返される。undefinedだから。試しにconsole.log(addArrow);とするとundefinedで返される。