コードレビューをしていると、いまだに `var` の亡霊や、グローバルスコープを平気で汚染する乱雑なスクリプトに遭遇することがある。
「動けばいい」というフェーズを過ぎた大規模フロントエンド開発において、変数の衝突や予期せぬサイドエフェクト(副作用)は、デバッグ工数を何倍にも膨れ上がらせる癌である。
V8エンジンのメモリ空間の挙動や、モダンブラウザのモジュールローディング機構を理解していれば、「なぜグローバル汚染が最悪なのか」「なぜ `const` をデフォルトとし、モジュールスコープを徹底すべきなのか」が物理的な必然として見えてくる。
今回は、レガシーなIIFE(即時実行関数式)の暗黒時代から、ES Modules(ESM)による現代的かつ堅牢な名前空間設計への移行戦略を、ランタイムの最適化視点を交えて徹底的に解説しよう。
—
1. なぜグローバル汚染と `var` は悪なのか:V8の視点から紐解くリスク
JavaScriptのランタイム(V8など)において、グローバルスコープに定義された変数や関数は、アプリケーションが生存している限りガベージコレクション(GC)の対象外となり、メモリのヒープ領域を半永久的に占有し続ける。さらに悪いことに、すべてのスクリプトから参照・書き換えが可能になるため、変数の衝突(Name Collision)が起きた瞬間にバグの特定が極めて困難になる。
また、`var` による変数宣言は「関数スコープ」を持ち、さらに「変数ホイスティング(巻き上げ)」を引き起こす。
// レガシーなコードレビューでよく見る地雷パターン
var user = “Alice”;
function doSomething() {
console.log(user); // ここで何が出力されるか?
// … 1000行のコード …
var user = “Bob”; // 巻き上げにより、関数全体の最初で ‘user’ が undefined で宣言されている扱いになる
console.log(user);
}
doSomething();
// 1回目は “undefined” が出力され、ジュニアエンジニアを絶望の淵に追いやる
この挙動は、コードの可読性を破壊するだけでなく、V8のJITコンパイラ(TurboFanなど)による最適化(形状の固定化やインラインキャッシュの効力発揮)を阻害する要因にもなり得る。スコープが曖昧な変数は、エンジンに余計な解析コストを払わせるのだ。
—
2. IIFE(即時実行関数式)という過去の遺産
ES Modulesが標準化される前、フロントエンドエンジニアたちはグローバル汚染を防ぐために IIFE (Immediately Invoked Function Expression) を駆使していた。
// レガシーなモジュールパターン(IIFE)
const UserModule = (function() {
// プライベート変数(外部からアクセス不可)
let currentUser = “Guest”;
function validate() {
return currentUser.length > 0;
}
// パブリックなAPIのみをオブジェクトとして返す
return {
login(name) {
currentUser = name;
console.log(`${currentUser} がログインしました。`);
},
getStatus() {
return validate() ? currentUser : “未認証”;
}
};
})();
UserModule.login(“Alice”);
console.log(UserModule.getStatus()); // “Alice”
なぜこのパターンから脱却すべきなのか?
IIFEはスコープを閉じる(Closures)ことでプライベート変数を実現した偉大な発明だが、現代の開発においては以下のデメリットが大きすぎる。
1. 依存関係の解決が手動: スクリプトの読み込み順序(`