JavaScriptの自己実行無名関数で起こる巻き上げ(ホイスティング)の罠とは?
まず、以下のサンプルコードを見てください。このコードを実行したときにどのような出力になるかを予測し、その理由を説明するのが今回の課題です。
var name = 'Zakir';
(() => {
name = 'Rahul';
return;
console.log(name);
function name(){
let lastName = 'Singh';
}
})();
console.log(name);
一見すると正しそうな「素朴な」読み方
このコードを行番号順に素直に追っていくと、次のように考えられます。
- 1行目 → グローバル変数nameに'Zakir'を代入
- 3行目 → 自己実行無名関数の中に入る
- 4行目 → 変数nameが'Rahul'に再代入される
- 5行目 → return文に到達するため、そこで関数を抜ける
- 15行目 → 現在の値である'Rahul'がコンソールに出力される
したがって、出力は次のようになるはずだと予想できます。
Rahul
しかし、残念ながらこれは誤りです。「変数と関数の巻き上げ(ホイスティング)」という概念を意識して、もう一度コードを読み直してみましょう。
正しい挙動を理解する:関数宣言の巻き上げ
1行目 → 変数nameに'Zakir'を代入。
3行目 → 自己実行無名関数の中に入る。
ここで重要になるのが関数の巻き上げです。自己実行関数の末尾で定義されているfunction name()は関数宣言であるため、実行前にそのスコープの最上部へと巻き上げられます。その結果、実行時のコードは概念的には次のような状態になっています。
例
let name = 'Zakir';
(() => {
let name;
name = 'Rahul';
return;
console.log(name);
name = function(){
let lastName = 'Singh';
}
})();
console.log(name);
注意すべき点は、変数や関数がスコープの先頭へ巻き上げられるとき、「定義」だけが先頭に移動し、「初期化(値の代入)」は元の位置で行われるということです。つまり、スコープの先頭時点ではローカル変数nameはundefinedですが、グローバル変数とは別個の変数として確かに存在しています。
そのため、name = 'Rahul'という再代入は、グローバル変数ではなく関数スコープ内のローカル変数nameに対して行われます。return文で関数を抜けた後に実行されるconsole.log(name)が出力するのは、依然として'Zakir'を保持しているグローバル変数nameです。よって、実際の出力は次のようになります。
出力
Zakir
ポイントのおさらい
- 関数宣言は、関数本体ごとスコープの先頭へ完全に巻き上げられる。
- varで宣言された変数も巻き上げられるが、初期化前の段階では
undefinedとなる。 - 関数名と変数名が同じ場合、関数宣言の巻き上げによってローカルスコープに同名の識別子が作られ、外側の変数は上書きされない。
このように、JavaScriptではコードの見た目どおりの順序で処理が実行されるとは限りません。巻き上げの仕組みを理解しておくことで、こうした一見不可解な挙動の原因を正確に把握できるようになります。
-
JavaScriptの無名関数(匿名関数)とは?基本の書き方と使い方をわかりやすく解説
無名関数(匿名関数)とはJavaScriptには、関数を定義する方法として「関数宣言」と「関数式」があります。このうち関数式は、関数宣言とほぼ同じ構文で記述でき、さらに次の2種類に分けられます。名前付き関数式:関数に名前が付けられているもの(コールスタック上で名前が表示されるため、デバッグ時に便利です)無名関数(匿名関数):関数に名前を付けずに定義するもの本記事では、後者の「無名関数」について、基本的な書き方と使い方を解説します。無名関数の基本的な書き方無名関数は、functionキーワードで関数を定義し、そのまま変数に代入する形で記述します。関数名を指定しない点が最大の特徴です。var my
-
JavaScriptのクラスで使う「get」キーワードとは?ゲッターの基本をわかりやすく解説
JavaScriptの「get」キーワードは、クラス内でゲッター(getter)関数を定義するために使われる構文です。C#やJavaなど、他のプログラミング言語でもおなじみの概念ですが、JavaScriptでも同様にプロパティのように値を取得できる便利な機能として提供されています。getキーワードの基本的な使い方クラス内でgetを使うと、メソッドでありながら、あたかもプロパティのようにアクセスできる関数を定義できます。以下のように記述します。class Employee { constructor(name) { this.name = na