【JavaScript】 即時関数の挙動について調べてみた
更新日:2021/08/23
JavaScriptには即時関数という関数がある。
普段何気なく使っていたので、今回は少し即時関数の挙動について調べてみました。
即時関数とは
即時関数は、関数宣言(定義)と関数呼び出し(実行)を一つの式でおこなう関数式です。
例えば次のようなコードがあるとします。
const a = "こんにちは" + function(){ return "○○です"; }();
function(){}()が、即時関数です。
function内のコードが即時に実行され、"○○です"が返されます。
その結果、aの内容は"こんにちは○○です"となります。
こんな使い方はほとんどありませんが、即時関数の例としてはわかりやすいと思います。
一般的によく使われる即時関数は、次のようなコードです。
即時関数のひな型
(function(){
// 一連のJavaScriptのコードを全て記述
})();
htmlの<script>タグ内のコードが、そのまま上の即時関数の中に記述されているのを目にします。
これは関数内の変数は外部から参照できないというJavaScriptのスコープ特性を利用して、グローバル汚染を防ぐための作法のようなものです。
例えば、次のようなコードがあるとします。
JavaScript : file1.js
let a = 0;
JavaScript : file2.js
let a = "a";
HTML : test.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./file1.js" type="text/javascript"></script>
<script src="./file2.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
file1.jsとfile2.jsはそれぞれ異なる目的を持ったスクリプトで、依存関係がないとします。
test.htmlをブラウザで表示させると、次のようなエラーがでて動作が停止します。
Firefox 開発ツールコンソール
SyntaxError: redeclaration of let a file2.js:1:1
Google Chrome DevTool コンソール
Uncaught SyntaxError: Identifier 'a' has already been declared
at file2.js:1
それぞれのスクリプトで、同じ名前の変数を定義しているのが原因です。
WordPressなどのCMSでは他者が作成した数多くのJavaScriptが読み込まれます。
変数名に安易なものを使用すると、このような問題に直面します。
他者が絶対に使用しないような変数名、例えば『affisaposvcomhensu_a』のように長くするなどで回避できますが、面倒ですね。
そこで次のように、即時関数を使用するのです。
JavaScript : file1.js
(function(){
let a = 0;
})();
JavaScript : file2.js
(function(){
let a = "a";
})();
HTML : test.html
省略
これで変数の被り(グローバル汚染)を気にしないで、プログラムできます。
即時関数の構造
即時関数は二つのパーツに分けることができます。
( | function(){・・・コード} | ) | ( ) | ||
↑ | ↑ | ||||
(1) | (2) |
(1) 関数定義
普通のfunction構文です。
(2) 関数の実行
関数は、「 ( ) 」をつけると実行されます。
次のような例を見ると、わかりやすいですね。
関数の実行
function a(){
// ・・・コード
}
a();
これをひとつにまとめたのが、即時関数です。
関数部分は丸カッコで囲む
お決まりの即時関数コードは、次のように関数部分を丸カッコで囲っています。
( function(){ } )();
これは必要なのでしょうか。
カッコをつけないコードに書き換えてみます。
JavaScript : file1.js
function(){
let a = 0;
}();
ブラウザで表示すると…
Firefox 開発ツールコンソール
SyntaxError: function statement requires a name
Google Chrome DevTool コンソール
Uncaught SyntaxError: Function statements require a function name
日本語訳:関数ステートメントには名前が必要です
名前をつけろと怒られてしまいました。
実はJavaScriptでは、空白を除いた行の先頭が function で始まると関数宣言となります。
関数宣言には、関数名が必要です。
では、名前をつけてみましょう。
JavaScript : file1.js
function f(){
let a = 0;
}();
ブラウザで表示すると…
Firefox 開発ツールコンソール
SyntaxError: expected expression, got ')'
Google Chrome DevTool コンソール
Uncaught SyntaxError: Unexpected token ')'
関数宣言した関数は、その場で呼び出すことができないのでエラーになります。
丸カッコで囲むと関数式とみなされ、関数オブジェクトを返します。
そのオブジェクトに対してなら、実行することが可能なのです。
関数宣言と関数式については、次のページを読んでみてください。
■【JavaScript】 関数の定義と変数代入(関数式)の違い
前の項目で、関数宣言と実行をまとめたものを即時関数と説明しましたが、次の例の方が適切かもしてません。
const f = function(){let a = 0;}; // 関数式で関数オブジェクト作成
f(); // 実行
ちなみに、関数宣言でなく式として認識させられればいいので、次のような書き方もできます。
( function (){
let a = 1;
}() );
このほかにも先頭に+や-などをつける方法があります。
しかし難読性を高める目的がないなら、慣例的な書き方、つまり定義部分を丸カッコで囲む書き方を使った方がよいです。
即時関数の実行結果
即時関数はグローバル変数の汚染を防ぐ目的だけでなく、なんらかの結果を得ることもできます。
しかしこのことが、JavaScripotコードの解読難易度を非常に高くしています。
特に初心者はお手上げ感MAXです。
例えば次のコードがあるとします。
let a = function(){return 1};
let b = (function(){return 1})();
違いが判りますでしょうか?
変数aは、関数オブジェクトを受け取っています。
a()で関数を呼び出すことができます。
変数bは、即時関数の実行結果を受け取ります。
よって変数bの値は1です。
このような短いコードならすぐに判別できますが、長くなってくると解読が難しくなりますね。
即時関数に引数を渡す
僕が最初にJavaScriptのコードを見て難しいと感じたのが、次のようなコードです。
(function(x,y,z){
// 数百行にわたる長いコード
})(value1,value2,value3);
今では理解していますが、当時はとても難解に感じました。
ただ単に、定義した関数に引数を渡しているだけです。
ここで、非常に大きな疑問が浮かんできます。
基本的に引数というものは、関数を様々なパターンで実行させるためのパラメータです。
しかし即時関数は、一度のみ実行されます。
次のように関数内でパラメータを指定しても、同じ結果を得られます。
(function(){
let x = value1;
let y = value2;
let z = value3;
// 数百行にわたる長いコード
})();
引き数引き渡しのパターンは、最終行をみないとどんな値をセットされているのかわかりません。
処理の先頭で明示的にセットされた方が、コードとしては読みやすいと思います。
しかし、【JavaScript】 ブラウザとサーバーサイド 共通コードで実行で次のようなコードを紹介しています。
(( name , func)=>{
if(typeof exports === 'object' && typeof module !== 'undefined'){
module.exports = func;
}else if( typeof define === "function" && define.amd ){
define(()=>{ return func;});
}else{
this[name] = func;
}
})(
"testFunc",
() =>{ // 関数本体
console.log("Load OK!!");
}
);
これ例では短いですが、実際のコードは紫色の関数本体が非常に長くなります。
そのため、即時関数内で定義するよりも、引数として渡した方がスッキリします。
引数で渡すか関数内で定義するかは、どちらがわかりやすいかで決めるといいですね。
即時関数の使用例
グローバル汚染を防ぐ以外の即時関数の使い方の例を挙げてみます。
例えば、次のようなコードがあるとします。
let a;
const value = b * c;
if( value < 0 ) { a = 1; }
else{
if( value < 5 ) { a = 2;}
else { a = 3;}
}
これを即時関数で記述してみます。
const a = (()=>{
const value = b * c;
if( value < 0 ) { return 1; }
if( value < 5 ) { return 2; }
return 3;
})( b , c );
即時関数内でリターンした値が、変数aにセットされます。
リターンした時点でコード即時関数内のコードが終了するので、スッキリとしました。
一度しか使わない変数を、即時関数内に閉じ込めることも可能です。
また最初の例では変数aをletで宣言していますが、即時関数を使用するとconstで宣言することができます。
これも即時関数の利点の一つです。
letやconstなどの変数宣言については、次のページを読んでみてください。
■【JavaScript】 適当に使ってた!変数宣言var/let/constの使い分け
即時関数イコール無名関数ではない
即時関数には無名関数が使用されることが多いです。
無名関数とは function(){} のように名前がついていない関数です。
即時関数はその場で一度だけ実行するのが目的なので、名前が必要でありません。
そのため無名関数を使用するのが合理的です。
しかし次のように関数に名前をつけても、即時関数として成り立ちます。
( function sokuzikansu(){
let a = 1;
})() ;
つまり即時関数イコール無名関数ではないということですね。
ただし、名前をつけたとしても後で使用することはできません。
次のようなエラーがでます。
sokuzikansu();
// 結果:ReferenceError: sokuzikansu is not defined
例外的に関数内部で、名前で呼び出すことができます。
const n = (function sokuzikansu(x){
return (x===1) ? 1 : x + sokuzikansu(x-1);
})(10);
console.log(n); // 結果: 55
再帰呼び出しになるので、フラグをしっかりと立てないとひどいことになりますね。
即時関数は関数ではない?
英語で即時関数は「Immediately Invoked Function Expression(IIFE)」と表記されます。
これを日本語にGoogle翻訳すると「すぐに呼び出される関数式」と翻訳されます。
なんだか違和感感じてモヤっとしませんか?
その正体は、これ
↓ ↓ ↓
( | function(){・・・コード} | ) | ( ) | ||
↑ | ↑ | ||||
関数式 | 関数式で作られた関数オブジェクトを実行 |
「すぐに呼び出される関数式」は、function(){} の部分です。
後ろの( )は、すぐに呼び出している部分です。
しかし、一般的に全てまとめて”即時関数”と呼ばれているのです。
「関数を実行する」という動詞なのに、”即時関数”という名詞であらわされているのが違和感なのです。
はげしくどうでもいい内容です。
即時関数は世間的に定着しているので、そのまま使っていればいい話です。
こんなこと考えているプログラマーは、仕事ができなそうですね。
更新日:2021/08/23
関連記事
スポンサーリンク
記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。