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 と呼ばれる安全策

関連リンク

関連記事