こんにちは!日々の開発、本当にお疲れ様です。
JavaScriptを書き始めたばかりの頃って、変数の動きやスコープ(変数がどこから見えるかという範囲)のルールに戸惑うこと、よくありますよね。「あれ? なんでさっき宣言した変数が見当たらないんだろう?」とか、「ループの中で変数を扱ったら、意図しない値になってしまった……」なんて経験はありませんか?
ここをクリアすれば、JavaScriptのコードが裏側でどう動いているのかの解像度がグッと上がり、一気に「できるエンジニア」の視界が開けますよ。今回は、かつてのJavaScript開発の現場でなくてはならない存在だった「IIFE(即時実行関数式)」を取り上げます。
「古い技術なんでしょ?」なんて侮ってはいけません。現代のモダンなJavaScript(`let` / `const`の時代)において、このIIFEがどのような役割に変化し、今なお私たちのコードをどう守ってくれているのか。その本質を、優しく紐解いていきましょう。
—
1. 変数スコープの歴史:かつてJavaScriptには「関数」しか壁がなかった
私たちが普段使っているJavaScriptの変数。まずは、その歴史を少しだけ振り返ってみましょう。
大昔(ES5、2015年以前)のJavaScriptには、変数を宣言する方法として `var` しか存在しませんでした。そして、この `var` には「関数スコープ」という大きな特徴がありました。これは、「変数の有効範囲を区切る壁を作れるのは、関数だけである」というルールです。
例えば、以下のようなコードを想像してみてください。
// 古き良き(そして恐ろしい)varの世界
function doSomething() {
if (true) {
var globalInDisguise = “私はどこからでも見えます”;
}
// なんと、if文やfor文のブロックを突き抜けて参照できてしまう!
console.log(globalInDisguise);
}
doSomething();
`if` 文や `for` 文の `{}`(ブロック)は、他の言語(JavaやC++など)であれば変数の壁になりますが、`var` の世界ではお構いなしにすり抜けて、外側のスコープを汚染していました。これでは、大規模なアプリを作るときに「うっかり同じ名前の変数を上書きしてバグが起きた……」という事故が頻発してしまいますよね。
—
2. 救世主としての「IIFE(即時実行関数式)」の誕生
「関数の中だけで有効な、一時的なスコープを作りたい!」
「グローバル空間(一番外側)の変数汚染を防ぎたい!」
そんな切実なプログラマーたちの願いから編み出されたのが、IIFE(Immediately Invoked Function Expression / 即時実行関数式)です。
まずはその基本的な書き方を見てみましょう。
// IIFEの基本形
(function() {
var privateMessage = “私は外からは絶対に触れない秘密の変数です”;
console.log(privateMessage); // 出力される
})();
// 外からこの変数にアクセスしようとすると…?
// console.log(privateMessage); -> ReferenceError: privateMessage is not defined
コードの構造を分解してみる
見慣れないうちは呪文のように見えるかもしれませんが、分解するととてもシンプルです。
1. `( function() { … } )` : 関数をカッコで囲むことで、JavaScriptパーサーに「これは関数宣言ではなく、関数式(値としての関数)ですよ」と伝えています。
2. `()` : その直後についている実行用のカッコによって、定義した瞬間その場で関数を呼び出しています。
「関数を作って、その場で即座に実行し、その中だけで閉じたスコープを作る」。これがIIFEの正体です。かつてのフロントエンド開発では、複数のライブラリを読み込んだときに変数名が衝突するのを防ぐため、コード全体をこのIIFEで包み込むのがお作法でした。
—
3. let / const 時代の到来:ブロックレベルスコープの勝利
さて、時代は流れてモダンなJavaScript(ES2015 / ES6以降)の時代になりました。
ここで `let` と `const` が登場します。
これらは、私たちが長年待ち望んでいた「ブロックレベルスコープ」をもたらしてくれました。つまり、`if` 文や `for` 文、あるいは単なる `{}` のブロックであっても、そこに `let` や `const` があれば、そこが立派な変数の壁になるのです。
// 現代のブロックレベルスコープ
{
const modernSecret = “私はブロックの中だけの秘密です”;
console.log(modernSecret); // 出力される
}
// ブロックの外からは見えない!
// console.log(modernSecret); -> ReferenceError
「おっ、じゃあもうIIFEなんて古い技術は使わなくていいんだね?」
そう思ったそこのあなた、鋭いですね!
確かに、単に「変数のスコープを狭めてグローバル汚染を防ぐ」という目的においては、単なるブロック `{ … }` や `let` / `const` がその座を奪いました。
しかし、IIFEは死んでいません。現代のJavaScriptにおいても、「即時実行して何かを初期化する」という文脈で、その役割を鮮やかに変化させて生き続けているのです。
—
4. 現代におけるIIFEの役割:非同期処理とプライベート変数のカプセル化
現代の開発現場において、IIFEがどのように使われているか、具体的なユースケースを2つ見てみましょう。
ユースケース A:トップレベル `await` が使えない環境での非同期処理
近年のNode.jsや最新のモダンブラウザでは、モジュールの最上位(ファイルのどこでも)で `await` が使えるようになりました(Top-level await)。しかし、古い環境や特定のバンドル設定、あるいは即座に非同期処理をカプセル化して実行したい場合、IIFEと `async/await` の組み合わせが非常に強力です。
// 現代のIIFE:非同期の初期化処理を即座に安全に実行する
(async () => {
try {
console.log(“データを取得中…”);
// 擬似的な非同期処理
const user = await fetchUserDataFromAPI();
console.log(`ようこそ、${user.name}さん!`);
} catch (error) {
console.error(“初期化に失敗しました:”, error);
}
})();
このパターンは、アプリケーションの起動スクリプトなどで、グローバルスコープを汚染せずに非同期の初期化シーケンスを走らせたいときに、今でも頻繁に使われます。
ユースケース B:クロージャを用いた「真のプライベート変数」の維持
クラス構文(`class`)が普及した現在でも、完全に外から隠蔽されたプライベートな状態を作りたいとき、IIFEとクロージャの組み合わせが役立ちます。
const CounterManager = (function() {
// この変数は外から絶対に直接書き換えられない(プライベート)
let count = 0;
return {
increment: function() {
count++;
console.log(`現在のカウント: ${count}`);
},
decrement: function() {
count–;
console.log(`現在のカウント: ${count}`);
},
getCount: function() {
return count;
}
};
})();
CounterManager.increment(); // 現在のカウント: 1
CounterManager.increment(); // 現在のカウント: 2
// count への直接アクセスはできないため、データの整合性が守られる
このように、モジュールシステムが完璧になかった時代からあるIIFEの「スコープを閉じ込める」という本質は、現代のクロージャの仕組みと結びついて、安全なカプセル化を実現するためのパターンとして生き続けています。
—
まとめ:言語の進化を知ることで、コードの意図が深く見えてくる
いかがでしたでしょうか?
- かつてのIIFE: `var` しかなかった時代に、関数スコープを作って変数汚染を防ぐための「必須の防壁」だった。
- 現代のIIFE: `let` / `const` やブロック文の普及により変数隠蔽の役割は譲ったが、「非同期の即時実行」や「クロージャによる強力なカプセル化」をスマートに行うための「高度なデザインパターン」へと進化を遂げた。
歴史的背景を知ると、目の前にあるコードが「なぜその形をしているのか」という設計者の意図が手に取るようにわかるようになります。こういう面白さを一つずつクリアしていくと、JavaScriptを書くのがもっともっと楽しくなりますよ。
ここをマスターできれば、あなたのJavaScriptの基礎力はもうバッチリです!自信を持って次のステップへ進んでいきましょう。それでは、また次回の記事でお会いしましょう!