【入門編】関数宣言と関数式:巻き上げの挙動が異なる理由をAST(抽象構文木)から理解する – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!JavaScriptの世界へようこそ。
フロントエンドの深淵からNode.jsのバックエンドまで、日々JavaScriptのコードを書き続けていると、時々「あれっ?」と首を傾げたくなる不思議な現象に出会いませんか?

その代表格が、「変数や関数を定義する前に呼び出せてしまう」という、JavaScript特有の挙動ですよね。他のプログラミング言語、例えばJavaやC++、Pythonなんかを触ってきた人なら、「定義する前に使うなんてエラーになるのが当たり前だろ!」と思われるはずです。でも、JavaScriptではそれが動いてしまったり、あるいは突然 `TypeError` を吐いて怒られたりする。

「なんでそんな気まぐれな動きをするの?」
「巻き上げ(Hoisting)って言葉は聞いたけど、イマイチ頭にしっくりこない……」

そんなモヤモヤを抱えているあなたへ。今回は、JavaScriptのエンジンがコードをどう読み解いているのか、その頭の中を覗き見できる「AST(抽象構文木)」という秘密道具を使って、この謎を根本から解き明かしていきましょう。

ここをクリアすれば、JavaScriptのコードが脳内で完全にトレースできるようになりますよ。さあ、一緒に本質の世界へ足を踏み入れましょう!

—

1. そもそも「巻き上げ(Hoisting)」ってなんだろう?

JavaScriptのコードは、私たちがエディタで書いた瞬間からそのまま実行されるわけではありません。ブラウザにせよNode.js(V8エンジン)にせよ、JavaScriptエンジンはコードを実行する前に、必ず「コンパイル(解析)フェーズ」という下準備の時間を設けています。

この下準備の段階で、エンジンはコード全体をざっと見渡し、「おっ、ここに変数があって、あそこに関数があるな」という情報をあらかじめメモ帳に書き留めておきます。これが、いわゆる「巻き上げ」の正体です。

言葉だけだとフワッとしているので、まずはよくあるコードを見比べてみましょう。

// 関数宣言は、定義する「前」に呼び出しても動く!
sayHello(); // 「こんにちは!」と出力される

function sayHello() {
console.log(“こんにちは!”);
}

「いやいや、定義する前に使ってるのに、なんで怒られないの?」って思いますよね。
では、これを関数式(変数に関数を代入する書き方)に変えてみるとどうなるでしょう?

// 関数式の場合は……?
sayHi(); // 💥 TypeError: sayHi is not a function

var sayHi = function() {
console.log(“やあ!”);
};

おっと、今度はエラーになってしまいました。「えっ、同じ関数なのに、書き方が違うだけで何でそんなに態度が変わるの?」――そう、ここが今回の最大のハイライトです。

この挙動の違いを正確に理解するために、JavaScriptエンジンが最初に作る「AST(抽象構文木)」の世界を覗いてみましょう。

—

2. AST(抽象構文木)からコードの構造を覗いてみよう

ASTというのは、JavaScriptエンジンが人間用のコードを「コンピュータが理解しやすいように木(ツリー)の形に分解したデータ構造」のことです。

例えば、先ほどの2つのコードが、エンジンの頭の中でどう解釈されているのかをイメージ図にしてみます。

パターンA:関数宣言の場合

function sayHello() {
console.log(“こんにちは!”);
}

エンジンがこれを見たとき、AST上では「関数全体が、名前(sayHello)というラベルが貼られた実体として、ひと塊でツリーのトップに登録」されます。
そのため、コンパイルが終わった瞬間から、「`sayHello`の中身はこれだ!」とエンジンが完全に把握しているため、コードのどの行から呼び出してもちゃんと動くのです。

パターンB:関数式(`var`)の場合

var sayHi = function() {
console.log(“やあ!”);
};

ここがポイントです。関数式の場合、ASTはこれを2つの別々の命令として分解します。
1. 変数宣言の登録: `var sayHi;` という箱だけを先につくる。
2. 値の代入: `sayHi = function() { … }` を、コードが上から順番に実行されるその時(実行フェーズ)に初めて行う。

つまり、最初にエンジンがメモ帳に書くのは、中身の関数ではなく「`sayHi`っていう名前の空っぽの箱があるよ」という情報だけなんです。
だから、定義するより上の行で `sayHi()` を呼び出すと、箱の中身はまだ `undefined`(未定義)なので、「おい、関数じゃないぞ!」と `TypeError` を食らってしまうわけですね。

—

3. なぜ「関数宣言」と「関数式」で挙動が異なるのか?

もう少し深掘りしてみましょう。
JavaScriptの歴史や設計思想において、「関数宣言(Function Declaration)」は、プログラム全体からいつでも参照できる「グローバルまたはスコープ内の公共財」として扱われるように設計されました。プログラムのどこに書いても、ファイル全体のどこからでも呼び出せるという利便性を最優先した名残です。

一方で、「関数式(Function Expression)」は、変数に代入される「データ(値)」の一種です。変数(`var`, `let`, `const`)のルールに従うため、「代入されるまでは存在しない(正確には中身が空っぽ)」という原則が厳格に適用されます。

ここで、モダンなJavaScript(ES6以降)における `let` や `const` を使った関数式も見ておきましょう。

// let や const で関数式を定義した場合
sayHey(); // 💥 ReferenceError: Cannot access ‘sayHey’ before initialization

let sayHey = function() {
console.log(“ヘイ!”);
};

今度は `TypeError` ではなく `ReferenceError`(参照エラー) になりました。
これは `let` や `const` が、初期化される前のアクセスを完全に禁止する「一時的デッドゾーン(TDZ)」という安全装置を持っているからです。JavaScriptの進化とともに、より安全にエラーを検知できるよう言語仕様が洗練されてきた証拠ですね。

—

4. 実務で絶対にハマらないためのベストプラクティス

ここまで読んで、「なるほど、関数宣言ならどこに書いても動くんだな!じゃあ全部関数宣言で書けば楽勝じゃん!」と思ったそこのあなた。ちょっと待ってください。

現代のプロフェッショナルな開発現場では、「関数宣言の巻き上げに頼りすぎるコードは、かえってバグの温床になる」として嫌われる傾向にあります。

コードを読む人間(チームのメンバーや未来の自分)の視点に立ってみてください。上から下へ自然に流れていくはずのコードが、あっちの行の関数が上に巻き上げられ、こっちの変数が……と追いかけ回すのは、メンタル的にかなり負担がかかりますよね。

そのため、実務では以下のルールを意識してみてください。

1. 基本は「関数式(特に `const` を使ったアロー関数など)」で統一する
変数のスコープルールが明確になり、上から順番に読めば必ず理解できる「予測しやすいコード」になります。
2. 依存関係を明確にする
使うものは必ず「使う前に定義する」という当たり前の順序を守ることで、予期せぬ巻き上げのバグを完全にシャットアウトできます。

// — 推奨されるモダンな書き方 —
const calculateTotal = (price, tax) => {
return price tax;
};

const displayResult = () => {
const result = calculateTotal(1000, 1.1);
console.log(`合計金額は ${result} 円です。`);
};

// 実行
displayResult();

このように、上から順に「道具(関数)を定義し、最後にそれを組み立てて実行する」というフローを意識するだけで、あなたの書くコードの品質は劇的に洗練されます。

—

まとめ

いかがでしたでしょうか?
今回は、AST(抽象構文木)の視点を交えながら、関数宣言と関数式の巻き上げのメカニズムを紐解いてきました。

  • 関数宣言は、AST解析の段階で中身の関数ごとツリーのトップに登録されるため、どこからでも呼び出せる。
  • 関数式(変数)は、宣言の箱だけが先に作られ、中身の代入は実行フェーズまで行われないため、定義前に使うとエラーになる。
  • モダンな開発では、巻き上げに頼らず、上から順番に読める `const` を使った関数式・アロー関数をベースにするのが定石。

ここをしっかりと自分のものにできれば、JavaScriptの変数スコープや実行順序で迷うことはもう二度とありません。
基礎の裏側にある「エンジンの挙動」までイメージできるようになったあなたなら、どんな複雑な非同期処理やフレームワークのコードに出会っても、怖気づくことなくスラスラと読み解いていけるはずです。

ここをクリアしたあなたなら、JavaScriptの基本はもうバッチリマスターできていますよ!自信を持って次のステップへ進んでいきましょう。

タイトルとURLをコピーしました