torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

JavascriptのIIFEについて考える

こんにちは、とりかつです。 最近、Javascriptで以下のようなコードを見かけました。

(function() {
  console.log("hogehoge");
})();

調べていくとこれはどうやら即時実行関数式(IIFE: Immediately Invoked Function Expression)[^1]というものだそうです。 私はぱっと見でIIFEの書き方が理解できませんでした。 なので、今回の記事ではこのIIFEを完全に理解したいと思います。

原点回帰

最初に関数の定義について振り返ります。 JSで関数式は以下のように定義, 実行できます。

// 関数の定義
var a = function() {
  return "done";
}

// 関数の実行
a();

aの後ろの()は関数呼び出し演算子です。 関数呼びだし演算子の構文は...(/* 引数 */)です。[^2]

関数の実行の流れは以下のような感じかと考えました。

  1. aが関数の実態と置き換わる
  2. 関数が実行される
  3. 関数の返り値の"done"に置き換わる

関数実行のイメージ
関数実行のイメージ
(もしかしたらこの説明は少し語弊があるかもしれません。あくまでイメージ程度で捉えていただければと思います。)

関数の実行時に変数が関数の実態と置き換わるイメージから、関数の定義と同時に関数の呼び出しをおこなうことができそうな感じがします。 やってみます。

関数を即時事項してみる
関数を即時実行してみる

... だめでした。 エラーにはUncaught SyntaxError: Function statements require a function nameとありました。 どうやら現在の書き方では関数式ではなく関数定義文として認識されていて、関数名が必要だと怒られているようです。

関数の定義を関数式として認識させる

ではどうしたら関数式として認識してもらえるようになるのでしょうか。 私は関数式という名前から関数を式として認識させればいいと考えました。 具体的には+, -, !のような演算子を関数の定義の前につければいいといった感じです。

プログラミング言語において式はオペレータとオペランドから成り立っています。 たとえば以下のような式を考えます。

1 + 6

この式では16オペランドで、+がオペレータです。 JSで関数定義を式として認識させるためには、JSエンジンに関数の定義がオペランドだよと教えてあげられれば良さそうです。

なのでオペレータを使って関数定義をオペランドとして認識できるようにします。

// 例
+ function() {
  return "done";
}

実行してみると...

関数をオペランドとして認識させる
関数をオペランドとして認識させる

うまくいきました。 現時点では関数呼び出しはしていないのでNaNが返ってきていることがわかります。 次に関数呼び出し演算子を使って関数を呼び出してみます。

関数の即時実行
関数の即時実行
うまくいきました! これで関数を定義して即時実行することができるようになりました。

グループ化演算子を使用する

関数の定義を式として認識させるためには、関数の定義がオペランドであることを明示すればよさそうでした。 JSのグループ化演算子(...)は「式での評価の優先順位を制御」[^3]する役割をもっています。

グループ化演算子は中に式が入ることが想定されています。 これよりグループ化演算子の中で関数を定義すれば、その関数は関数式として扱われると考えられます。 実際にやってみます。

グループ化演算子を使って関数を定義する
グループ化演算子を使って関数を定義する
グループ化演算子を使って関数を式として定義することができました。

もちろん、即時実行も可能です。

グループ化演算子を用いて関数を即時実行する

グループ化演算子を使う理由

ここまで、演算子を用いた関数式の定義と関数の即時実行について考えてきました。 私がIIFEを理解できなかったポイントは、なぜグループ化演算子を用いるのかという点です。 なので最後になぜグループ化演算子を用いるのか考えていきます。

グループ化演算子は静的なスコープをもっているようです。 そのため、以下のケースではエラーを吐かれます。

グループ化演算子ありの場合

このようにグループ化演算子内で宣言する前の変数にはアクセスできないようです。 これは名前空間を切り分けることに役立ちそうです。

実際の開発では複数人で開発を進めていきます。さらにさまざまなパッケージを使用することが考えられます。 なので、名前空間は非常に重要となってきます。

以上より、JSファイルが読み込まれた際に名前空間を切り分ける目的でグループ化演算子が用いられていると考えられます。 逆に名前空間を切り分ける必要がないばあいに関数を即時実行したければ使用する演算子はなんでもいいかもしれません。

おわりに

今回はJSのIIFEを切り口にJSの関数について考えました。

個人的に、演算子を用いることで関数を式として認識させるという部分は大きな再発見でした。

参考