IIFE(即時実行関数式)とは何か ― なぜ昔は使われ、今ES Modules時代に減ったのか
結論
IIFE(Immediately Invoked Function Expression、即時実行関数式) とは、定義した直後にその場で呼び出す関数式のこと。(function(){})() の形で書く。ES5 以前は モジュールスコープを作るための定番パターンだったが、ES Modules (ESM) が普及した今は出番が大きく減った。
// 基本形
(function() {
console.log('実行された')
})()
// 出力: 実行された
// 引数を渡せる
(function(name) {
console.log(`hello, ${name}`)
})('world')
// 出力: hello, world
レガシーコードや jQuery プラグイン、CDN 配布の JavaScript ライブラリで今でも見かける。
なぜ昔は IIFE が必要だったのか
ES5 以前の JavaScript には モジュールシステムがなかった。<script> タグで読み込んだファイルはすべて 同じグローバルスコープを共有する。
// foo.js
var counter = 0
// bar.js
var counter = 100 // foo.js の counter を上書き!
別々の作者が書いた js ファイルが同じ名前の変数を使うと衝突する。これを避けるために、1つのファイル全体を関数で囲んで、ローカル変数として閉じ込めるのが IIFE。
// foo.js
(function() {
var counter = 0 // IIFE の中だけのローカル変数
// ...
})()
// bar.js
(function() {
var counter = 100 // 別の IIFE のローカル変数。衝突しない
// ...
})()
これが「IIFE はモジュールの代用品」と言われる理由。
構文のバリエーション
IIFE には複数の書き方がある。どれも同じ意味だが、好みやコーディング規約で変わる。
// 1. 関数式を括弧で囲む(最も一般的)
(function() {
console.log('A')
})()
// 2. 関数式全体を括弧で囲む
(function() {
console.log('B')
}())
// 3. 単項演算子で関数式扱いにする
!function() {
console.log('C')
}()
+function() {
console.log('D')
}()
~function() {
console.log('E')
}()
// 6. アロー関数 IIFE(ES6+)
(() => {
console.log('F')
})()
// 7. async IIFE(ES2017+、トップレベル await が使えない環境向け)
;(async () => {
const data = await fetch('/api/data')
console.log(await data.json())
})()
!function +function 系は、ファイル先頭で 直前のステートメントとの繋がりを断つために使われた歴史がある。たとえば前のファイルの末尾がセミコロンなしだった場合、(function(){...})() がリテラル呼び出しと誤認されないように、行頭に ; を付ける慣習もある。
;(function() {
// ...
})()
これは「defensive semicolon」と呼ばれる安全策。Minification で改行が消えても安全に動く。
IIFE の典型的な用途
1. プライベートスコープを作る(クラシック)
var myModule = (function() {
// ここはローカルスコープ
var privateCounter = 0
return {
increment: function() { privateCounter++ },
getCount: function() { return privateCounter }
}
})()
myModule.increment()
myModule.getCount() // 1
myModule.privateCounter // undefined(アクセス不可)
これが「revealing module pattern」と呼ばれる古いモジュール構成の基本形。今は ES Modules の export で同じことができる。
2. ループ変数のキャプチャ問題を回避
ES5 までは var のホイスティングによってループ内のクロージャが意図せず共有された。
// ES5: 全部 3 が出力される
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i) }, 0)
}
// 3, 3, 3
// ES5 + IIFE: 0, 1, 2 が出力される
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() { console.log(j) }, 0)
})(i)
}
// 0, 1, 2
ES6 以降は let を使えば同じことができる:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// 0, 1, 2
3. async IIFE でトップレベル await を回避
ES2022 以降、ES Modules ではトップレベル await が使える。だが CommonJS や古い環境では使えないので、async IIFE で代用する。
;(async () => {
const data = await fetch('/api/data')
console.log(await data.json())
})()
CLI スクリプトの先頭でよく使う。
なぜ今は使われなくなったのか
理由1: ES Modules が普及した
// modern.mjs
const counter = 0 // モジュールスコープに閉じている
export function increment() { /* ... */ }
ES Modules のファイルは デフォルトでスコープが閉じる。IIFE でラップする必要がない。
理由2: let / const がブロックスコープを提供する
var のホイスティング問題が let/const で解消したので、ループ内クロージャ回避の IIFE は不要。
理由3: トップレベル await の登場
ES Modules では await をトップレベルで書ける(Node.js 14.8+、最新ブラウザ)。
// index.mjs
const data = await fetch('/api/data')
console.log(await data.json())
いまだに見かける場面
- CDN 配布の UMD ライブラリ(jQuery プラグイン、d3.js のサンプル等): ブラウザ・Node 両対応のために IIFE で囲む
- bookmarklet:
javascript:(function(){...})()でブラウザのアドレスバーから実行 - 古い CMS のテンプレートに直接書く JS: モジュールシステムが使えない環境
- ESM 非対応の古い CommonJS パッケージで async 処理を即実行したいとき
まとめ
- IIFE = 定義した直後にその場で呼ぶ関数式。
(function(){})()または(()=>{})() - ES5 以前の モジュールスコープの代用品として広く使われた
- ES Modules +
let/const+ トップレベルawaitの登場で、新規コードではほぼ不要 - レガシーコード読解・CDN ライブラリの理解には今でも必要な知識
;(function(){})()の先頭セミコロンは defensive semicolon と呼ばれる安全策