なぜJavaScriptでは関数をconstで定義するのか?functionとの違い・メリット・使い分け

なぜJavaScriptでは関数をconstで定義するのか?functionとの違い・メリット・使い分け

なぜJavaScriptでは関数をconstで定義するのか?functionとの違い・メリット・使い分け

JavaScriptで関数を定義する際、「function宣言」ではなく const foo = () => {} のような関数式が一般的になりつつあります。

この記事では、なぜ現場では const 関数式が推奨されるのか、巻き上げや再代入防止といった仕様面から、ReactやTypeScriptとの相性、ベストプラクティスまで体系的に解説します。

目次

なぜJavaScriptでは関数をconstで定義するのか?モダン開発における標準スタイルの理由

フロントエンド現場では、const foo = () => {} のような関数式を採用するコードベースが主流になりました。React・Vue・Svelte などのコンポーネント指向フレームワークでは、再レンダリングや副作用管理の明示性が求められるため、巻き上げを伴わない関数宣言が分かりやすく評価されるからです。

さらに const で束縛すれば再代入不可というイミュータブル設計になり、チーム開発で頻出する「意図しない上書きバグ」を防止できます。TypeScript を導入すると型推論が強化され、コンパイル時に早期エラー検出が可能になる点も人気の理由です。こうした背景から「まずは const 関数式で書く」という方針が定番になっています。

巻き上げと Temporal Dead Zone:const は「巻き上がるが初期化されない」

ECMAScript 仕様上、varletconst で宣言された識別子は読み込み時にメモリへ登録されるため、const 自体も巻き上げ対象です。

しかし初期化完了前にアクセスすると ReferenceError となり、これをTemporal Dead Zone(TDZ)と呼びます。

関数宣言は定義と同時に初期化されるため、宣言より前から呼んでも問題ありませんが、const 関数式では次のような違いが生じます。

// TDZエラーになる例
console.log(greet); // ReferenceError
const greet = () => console.log("Hi");

// 正常なパターン
const hello = () => console.log("Hello");
hello(); // "Hello"

この挙動により実行順序がコード上の記述と一致し、大規模プロジェクトでも「定義位置=呼び出し可能ライン」という単純で追いやすいルールが成立します。

アロー関数の this と arguments:レキシカル束縛が生むメリットと注意点

アロー関数は this を外側スコープから継承するうえ、arguments もレキシカルです。そのため可変長引数を扱う場合は ...rest 構文を使う必要があります。

通常の関数式(const fn = function() {})は従来の関数宣言と同じく thisarguments を動的に束縛するため、古い API やコンストラクタのコールバックに適しています。

一方、React コンポーネントのイベントハンドラや Promise チェーン内部処理ではレキシカル this が好都合で、冗長な .bind() を排除できます。

用途に応じた宣言形式の使い分けが、可読性と安全性を同時に高める鍵です。

パフォーマンスと実行エンジン最適化:関数名推論とデバッグ情報の違い

V8・SpiderMonkey・JavaScriptCore などのエンジンは、関数宣言も関数式も内部的には同じコードオブジェクトとして扱います。通常の実行速度に顕著な差はありませんが、関数宣言は読み込み直後にオブジェクト化されるため、インライン展開や JIT の事前最適化パスに乗りやすいケースがあります。

反面、不要に巻き上げられることでメモリを消費する危険もあります。一方 const 関数式は実行時にオブジェクト生成されるため、デバッグ時スタックトレースで関数名推論(V8 の %FunctionGetName 内部命令)の恩恵を受けやすい利点があります。

結局は保守コストと性能ボトルネックのどちらを優先するかで選択が変わります。

ES Modules 時代のスコープ:トップレベルは常にモジュールスコープ

ES2015 以降、<script type="module"> が一般的になり、トップレベルもブロックスコープ化されました。export function foo()export const bar = () => {} は import 側から同等に扱えます。

ただし Rollup や ESBuild の tree‑shaking では副作用判定が容易な const 代入式のほうがバンドルサイズ削減につながる場合があります。

モジュールスコープでは「公開 API → 関数宣言」「内部ユーティリティ → const 関数式」という切り分けが実践的なガイドラインです。

ユースケース別・最適な宣言方式早見表

利用シーン推奨形式主な理由
ライブラリの公開 APIfunction 宣言読み込み直後に呼び出せる/APIドキュメントで認識しやすい
→ import 直後でも即利用可
モジュール内ユーティリティconst 関数式再代入防止・巻き上げタイミングを制御
→ バグ低減と静的解析の親和性
React/Vue イベントハンドラアロー関数this バインディング不要・レンダリング最適化
→ 冗長な .bind() を省ける
コンストラクタ関数通常関数式 / 宣言new でのインスタンス化が必要
→ オブジェクト生成に対応

ベストプラクティスと ESLint / TypeScript 設定例

コードスタイルガイドを CI に組み込むと、宣言形式の揺れを自動検出できます。以下のような設定を .eslintrc.json(またはプロジェクトによっては .eslintrc.js)に記述しましょう。

{
  "rules": {
    "func-style": ["error", "expression"],
    "prefer-const": "error",
    "no-redeclare": "error",
    "no-var": "error"
  }
}

func-style を「expression」に統一することで「関数は関数式で書く」方針を貫徹できます。TypeScript では no-redeclareno-var を合わせて有効化し、スコープ再宣言や古い変数宣言を禁止すると安全性がさらに向上します。Prettier と連携すればフォーマットを自動整形でき、レビュー工数を大幅に削減できます。

まとめ:意図を明示した宣言方式がバグを減らし開発体験を高める

const 関数式は「再代入不可」「TDZ による実行順保証」「レキシカル this/arguments」といった特徴で、モダンフロントエンドのバグ発生要因を抑えます。

従来の関数宣言はライブラリ API やコンストラクタ用途で依然として有用です。パフォーマンス差は限定的ですが、デバッグ情報や bundle 最適化への影響など細部の違いは無視できません。

最終的には「なぜその形式を選んだか」をコメントやドキュメントに残し、チーム全体で共通認識を持つことが開発体験と品質向上の最短ルートです。

// ✅ ユーティリティ関数には const 関数式を使用
const formatDate = (date) => date.toISOString().slice(0, 10);

// ✅ API関数は function 宣言(巻き上げが有利)
export function validateInput(input) {
  return typeof input === "string";
}

JavaScriptの変数宣言(var・let・const)の違い【それぞれの役割とは?】

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次