JavaScriptの即時関数(IIFE)とは?書き方と先頭の「!」やカッコの意味

#JavaScript
にメンテナンス済み
記事のトップ画像

即時関数(IIFE)とは

即時関数(IIFE: Immediately Invoked Function Expression)は、定義と同時に実行される関数です。関数式を () で囲み、その直後に () を付けて呼び出します。

(function () {
  console.log('定義と同時に実行されます');
})();

// アロー関数でも書けます
(() => {
  console.log('こちらも即時実行されます');
})();

即時関数にしても、処理の実行順序は変わりません。次のコードは、関数を使わず直接 console.log を3つ並べた場合と同じ結果になります。

console.log('スクリプトの先頭です。');
(function () {
  console.log('スクリプト実行中。');
})();
console.log('スクリプトの末尾です。');
スクリプトの先頭です。
スクリプト実行中。
スクリプトの末尾です。

なぜ必要なのか:変数スコープを閉じ込める

即時関数の目的は、変数のスコープを関数内に制限し、グローバル変数の汚染を防ぐことです。

関数の外で var を使って変数を定義すると、その変数はスクリプト全体(ブラウザではグローバル)から参照できてしまい、他のスクリプトの変数と衝突する恐れがあります。

var foo = 'スクリプトの途中で定義されました';
console.log('スクリプト実行中。', foo);
console.log('スクリプトの末尾です。', foo); // どこからでも参照できる

即時関数の中で定義した変数は、関数の外からは参照できません。

(function () {
  var foo = '即時関数の中で定義されました';
  console.log('スクリプト実行中。', foo);
})();
console.log('スクリプトの末尾です。', foo);
スクリプト実行中。 即時関数の中で定義されました
Uncaught ReferenceError: foo is not defined

即時関数の省略記法(先頭の「!」の意味)

function 宣言は文の先頭に書くと「関数宣言」として解釈されてしまい、そのままでは () で呼び出せません。そのため、関数を「式」として扱わせることで即時実行を成立させます。カッコ (...) で囲むのが最も一般的で、ほかにも単項演算子を先頭に付ける方法があります。

(function () {
  const foo = 'a';
})();
!(function () {
  const foo = 'b';
})();
+(function () {
  const foo = 'c';
})();
-(function () {
  const foo = 'd';
})();
void (function () {
  const foo = 'e';
})();

いずれもエラーにならず、関数が即時実行されます。! は文字数が少なくて済むため、圧縮されたスクリプトでよく見かけます。

! や + を付けた場合、即時関数の返り値に演算子が適用されます(! なら真偽値の反転)。返り値を使いたい場合は、カッコで囲む書き方を使ってください。

また、前の行がセミコロンで終わっていないと、先頭の ( が前の式の関数呼び出しとして解釈される事故が起きます。即時関数の前の行には必ずセミコロンを付けましょう(先頭に ; を置く書き方もあります)。

現在でも即時関数は必要?

ES Modules(<script type="module"> や import / export)では、ファイル自体が独立したスコープを持つため、スコープを閉じ込める目的の即時関数は基本的に不要です。また、let / const はブロックスコープなので、{ ... } で囲むだけでも変数を閉じ込められます。

一方で、次のような場面では今でも使われます。

  • 通常のスクリプト(モジュールでない <script>)で、グローバルを汚染したくない
  • トップレベル await が使えない環境で、async 即時関数を使う
(async () => {
  const response = await fetch('/api/data');
  console.log(await response.json());
})();