即時実行関数式(IIFE)とは何か?JavaScript で書く基本と応用

概要
JavaScript には即時実行関数式(IIFE)[1]という関数式が存在します。
定義されるとすぐに実行される特徴があり、即時実行関数式を使うと様々な応用ができます。
今回の記事では JavaScript の即時実行関数式に焦点を当て、その応用例について考えていきたいと思います。
この記事を読んでわかること
- JavaScript の即時実行関数式の使い方
対象読者
- JavaScript 初心者
- JavaScript にある程度馴染みのある開発者
前提知識・環境
- JavaScript の文法の基本的な知識
即時実行関数式とは何か?
まずは、即時実行関数式の最も簡単なサンプルコードを書いて挙動を確認します。
const sayHello = (() => {
console.log('Hello!');
})();
一見、定数とアロー関数による関数式を定義しているように見えますが、アロー関数の部分を () で囲い、さらに末尾に () を記述しています。こちらのコードをそのまま実行すると、コンソールに Hello! と表示されます。
即時実行関数式とは名前の通り、宣言と同時に実行される関数のことです。
ただ、これだけ聞くとシンプルに関数内に記述された console.log('Hello!') などの処理をそのまま実行するのと何も変わりがありません。次に即時実行関数の有効な使い方について考えてみます。
即時実行関数式の有効な使い方
即時実行関数式の挙動についてさらに理解を深めるために、即時実行関数式ではない下記のコードの挙動について確認します。
即時実行関数でない関数式
const uniqueInteger = () => {
let counter = 0;
return () => counter++;
};
console.log(uniqueInteger()()); // 0
console.log(uniqueInteger()()); // 0
console.log(uniqueInteger()()); // 0
uniqueInteger 関数は内部で counter という変数をもち、返値に counter 変数をインクリメントした結果を返す関数を返します。少しややこしいですが、関数を返値とする関数であるため counter の値を取得するには、uniqueInteger()() のように () を連続して書いてあげる必要があります。
uniqueInteger 関数は呼び出されるたびに、counter 変数を 0 で初期化するため、何度 uniqueInteger()() を実行してもその結果は 0 となります。
上記のサンプルコードを即時実行関数式に書き換えます。
即時実行関数式である関数式
const uniqueInteger = (() => {
let counter = 0;
return () => counter++;
})();
console.log(uniqueInteger()); // 0
console.log(uniqueInteger()); // 1
console.log(uniqueInteger()); // 2
記事冒頭の sayHello 関数と同様に、アロー関数の部分全体を () で囲い、末尾に () を追加しました。
uniqueInteger 関数は即時実行関数であるため、宣言と同時に実行され、uniqueInteger には () => counter++ という関数が格納されている状態です。したがって、counter 変数の値は uniqueInteger() と記述するだけで取得することができます。
この場合、実行する度に結果が 0, 1, 2 のように変化していることがわかります。
uniqueInteger 関数は宣言と同時に実行されたため、内部の counter 変数を初期化することなく参照し続けます。
これにより、uniqueInteger 関数は実行の度に初期値 0 とした counter 変数をインクリメントした値を返し、カウンターとしての役割を果たします。
以前、下記の記事の中で関数のプロパティを使ってカウンターを実装しました。
const uniqueInteger = () => uniqueInteger.counter++;
uniqueInteger.counter = 0; // 初期値の宣言
関数のプロパティを使った方法では、関数の宣言とは別にプロパティの値を初期化してあげる煩わしさがあります。
即時実行関数式を用いた記述方法では、関数宣言を行うだけでカウンターとして機能するためよりシンプルに記述することができるわけです。
さらに、即時実行関数式を使えばこのカウンターに色々な機能を付与することができます。
即時実行関数式を使った応用例
即時実行関数式として記述した uniqueInteger 関数の返値を書き換えてみます。
const uniqueInteger = (() => {
let counter = 0;
return {
count: () => counter++,
current: () => counter,
reset: () => (counter = 0),
};
})();
console.log(uniqueInteger.count()); // 0
console.log(uniqueInteger.count()); // 1
console.log(uniqueInteger.count()); // 2
console.log(uniqueInteger.current()); // 3
console.log(uniqueInteger.current()); // 3
console.log(uniqueInteger.reset()); // 0
console.log(uniqueInteger.current()); // 0
返値をオブジェクト形式に変更し、それぞれのプロパティで異なる関数式を格納しています。
このサンプルコードでは uniqueInteger 関数は宣言と同時にオブジェクトが格納されるため、uniqueInteger.count() を実行すれば counter の値を返してからインクリメントを実行し、uniqueInteger.current() とすれば現在の counter 変数の値を返し、uniqueInteger.reset() で counter の値を初期化します。
このように、uniqueInteger という1つの即時実行関数に様々な機能を付与することができました。
さらに下記のように、即時実行関数式を返す関数式を定義すれば個別のカウンターインスタンスを作成することができます。
const uniqueInteger = () =>
(() => {
let counter = 0;
return {
count: () => counter++,
reset: () => (counter = 0),
current: () => counter,
};
})();
const counterA = uniqueInteger();
const counterB = uniqueInteger();
console.log(counterA.count()); // 0
console.log(counterA.count()); // 1
console.log(counterA.count()); // 2
console.log(counterB.count()); // 0
console.log(counterB.count()); // 1
console.log(counterB.count()); // 2
counterA, counterB をそれぞれ別の処理の途中に挿入しておけば、「処理Aと処理Bはそれぞれ何回実行されたか?」ということが直感的にわかりやすくなります。
最後まで読んでくださりありがとうございました🙇♂️
記事冒頭のサンプルコードのような記述では利用用途が不明な即時実行関数式ですが、うまく使えばとても便利に扱うことができます。
今回の記事では「即時実行関数式」に焦点を当ててきましたが、記事後半のサンプルコードのように関数内の関数を参照するような書き方はクロージャ[2]と呼ばれます。今回の内容への理解を深めるためにもクロージャについて調べてみるのも良いかもしれません。
Discussion