即時関数はこのようなものですね
// function
(function () {
console.log("foobar");
})();
// アロー
(() => {
console.log("foobar");
})();
ワンセットで覚えても良いですが, それでは応用が効きませんし, 何よりJavascriptの関数の文法理解が進んでいません. というわけでこれを分解してみます.
構造
// ↓ 関数本体 ↓ ↓呼び出し
( function () {} )()
//↑ 関数式を評価 ↑
// 改行する
(
function () {
// ...
}
)() // ← この()が関数の呼び出し
つまり, 関数本体と, それを囲む括弧と, その関数を呼び出すための括弧で構成されています.
最初の括弧の役割
//↓ ここ ↓
( /*...*/ )();
この括弧は, ( 式 )
という構造をとります. この括弧は演算子であり, ( 式 )
とすることで四則演算のときと同じように評価され, 結果が返ってきます. 即時関数を使うには, この評価されるというのが非常に重要です.
functionの場合
(1)
の結果が1, ( 数値 )
は入れた数値そのものが返ってくるように, ( 関数 )
とすればその関数が返ってきます. そのまま同じものが返ってくるのであれば括弧を省略できそうな雰囲気があります.
function () {
// ...
}();
// Uncaught SyntaxError: Function statements require a function name
function a() {
// ...
}()
// Uncaught SyntaxError: Unexpected token ')'
しかし, これはできません. 無名関数とするには, その関数が式として評価される必要があり, 関数宣言として評価されてしまうと無名にできません.
式の場合, すぐに呼び出す, 代入によって後から間接的に名前が付くなどあります. しかし, 関数の宣言としか評価できない場合は名前がないと使えないためです.
function a() {} // 関数の宣言. 名前が必須
{
function b() {} // 関数の宣言 名前が必須
}
(function () {}) // 括弧内は式 名前が不要. (function a() {}) も文法上は可. やらないけど.
hoge(function() {}, ...) // 実引数はそれぞれが式 名前が不要. ちなみにカンマ(,)も演算子. コールバック関数入れたりはこれ
const fuga = function () {}; // = の右辺は式
また, 関数が式の場合, 評価されると関数が作成されます. しかし, function a() {}()
は式として評価しきっていないのに呼び出そうとしている状態で, 文法上間違いです (若干語弊があるかもその1).
呼び出せる状態にするためにはどうすればよいかというと, 関数の式を評価したものを利用すれば良いです (若干語弊があるかもその2).
function a() {
return function () {}; // 返すときに評価される
}
a()(); // returnで評価済みのものを () で呼び出せる
const c = a(); c(); // 代入時に右辺を評価して, その結果をcに代入. それをc()で呼び出し
const b = function () {}; // 代入時に評価される
b(); // 評価されたものが入っているので呼び出せる
(function () {})() // 括弧はその中を評価するので, 括弧の外では評価済みになる. そのため, (関数)()で呼び出せる. 即時関数!!!!
((((function () {}))))() // もちろん評価済みをもう一度評価しても良いのでこれもいける. 限界を知りたいが, 限界を知った記事があったような気がする
アロー関数の場合
アローの場合, functionと違って必ず式になります. 宣言ではないため直接名前をつけるのは無理で, 代入をすることで間接的に名前をつけます.
() => {}; // 式
{
() => {}; // 式
}
(() => {}); // 括弧内は式
hoge(() => {}); // 実引数は式
const fuga = () => {}; // = の右辺は式
functionと同様に, 式として評価してしまえば同じように括弧をつけると呼び出せます.
function a() {
return () => {}; // 返すときに評価される
}
a()(); // returnで評価済みのものなので () で呼び出せる
const b = () => {}; // 代入時に評価される
b(); // 評価されたものが入っているので呼び出せる
(() => {})() // 括弧はその中を評価するので, 括弧の外では評価済みになる. そのため, (関数)()で呼び出せる. 即時関数!!!!
((((() => (() => (1)))))())() // ひええええ
応用
即時関数は, 関数を作ってすぐに呼び出している, という構造であると把握すれば少し応用がききます.
const hoge = 100;
((foo) => {
const fuga = foo + 200; // 100 + 200が代入される
})(hoge)
これは,
(foo) => {
const fuga = foo + 200;
}
という関数がまずあって, これを ( 関数 )
で評価し, (hoge)
で呼び出している, という構造だとわかります. jQueryで$
が衝突するときにたまに使うことがあると思います.
また, 次の2つはhogeという名前の変数を作る以外の挙動が同じです (多分速度が違うとかは無しで). というよりステップを増やしただけです
// 代入して呼び出し
const hoge = (a) => { console.log(a); }
hoge(5);
// 即時関数
((a) => {
console.log(a);
})(5);
さいごに
院時代に研究でJavaのASTを触っていましたが, すでに色々とうろ覚え状態だったので間違っている部分があるかもしれません. ご指摘いただけると嬉しいです.
プログラミング始めたてだと, 関数の呼び出し結果ではなく, 本体を代入できることを知らなかったりすると思いますし, 自分がそうだった時代が懐かしいです.