MENU

JavaScript関数・メソッド

【JavaScript】 即時関数の挙動について調べてみた

更新日:2020/02/29

 

JavaScriptには即時関数という関数がある。
普段何気なく使っていたので、今回は少し即時関数の挙動について調べてみました。

 

■お願い
去年ECMAScript2020を頑張って日本語訳しましたが、誰も見てくれません・・・
誰かみて!!
【JavaScript】 学習のためECMAScript2020を日本語訳してみました

即時関数とは

 

即時関数は、関数宣言(定義)と関数呼び出し(実行)を一つの式でおこなう関数式です。

 

例えば次のようなコードがあるとします。

 

const a = "こんにちは" + function(){ return "○○です"; }();

 

function(){}()が、即時関数です。

 

function内のコードが即時に実行され、"○○です"が返されます。
その結果、aの内容は"こんにちは○○です"となります。

 

こんな使い方はほとんどありませんが、即時関数の例としてはわかりやすいと思います。

 

 

一般的によく使われる即時関数は、次のようなコードです。

 

JavaScript

(function(){
    // 一連のJavaScriptのコードを全て記述
})();

 

これは関数内の変数は外部から参照できないという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(); // 実行

 

ちなみに、関数宣言でなく式として認識させられればいいので、次のような書き方もできます。

 

JavaScript

( function (){
    let a = 1;
}() );

 

このほかにも先頭に+や-などをつける方法があります。

 

しかし難読性を高める目的がないなら、慣例的に定義部分を丸カッコで囲む書き方を使った方がよさそうです。

 

即時関数の実行結果

 

即時関数はグローバル変数の汚染を防ぐ目的だけでなく、なんらかの結果を得ることもできます。

 

しかしこのことが、JavaScripotコードの解読難易度を非常に高くしています。
特に初心者はお手上げ感MAXです。

 

例えば次のコードがあるとします。

 

JavaScript

let a = function(){return 1};
let b =  (function(){return 1})();

 

違いが判りますでしょうか?

 

変数aは、関数オブジェクトを受け取っています。
a()で関数を呼び出すことができます。

 

変数bは、即時関数の実行結果を受け取ります。
よって変数bの値は1です。

 

このような短いコードならすぐに判別できますが、長くなってくると解読が難しくなりますね。

 

即時関数に引数を渡す

 

僕が最初にJavaScriptのコードを見て難しいと感じたのが、次のようなコードです。

 

JavaScript

(function(x,y,z){
// 数百行にわたる長いコード
})(value1,value2,value3);

 

今では理解していますが、当時はとても難解に感じました。
ただ単に、定義した関数に引数を渡しているだけです。

 

ここで、非常に大きな疑問が浮かんできます。

 

基本的に引数というものは、関数を様々なパターンで実行させるためのパラメータです。

 

しかし即時関数は、一度のみ実行されます。
次のように関数内でパラメータを指定してもいいのです。

 

JavaScript

(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;

 

if( b === c ) { a = 1; }
else{
  if( b === d ) { a = 2;}
  else { a = 3;}
}

 

これを即時関数で記述してみます。

 

const a = (()=>{
        if( b === c ) { return 1; }
        if( b === d ) { return 2; }
        return 3;
   })();

 

即時関数内でリターンした値が、変数aにセットされます。
リターンした時点でコード即時関数内のコードが終了するので、スッキリとしました。

 

また最初の例では変数aをletで宣言していますが、即時関数を使用するとconstで宣言することができます。
これも即時関数の利点の一つです。

 

関連記事:【JavaScript】 適当に使ってた!変数宣言var/let/constの使い分け

即時関数イコール無名関数ではない

 

即時関数には無名関数が使用されることが多いです。

 

無名関数とは function(){} のように名前がついていない関数です。

 

即時関数はその場で一度だけ実行するのが目的なので、名前が必要でありません。
そのため無名関数を使用するのが合理的です。

 

しかし次のように関数に名前をつけても、即時関数として成り立ちます。

 

JavaScript

( function sokuzikansu(){
    let a = 1;
})() ;

 

つまり即時関数イコール無名関数ではないということですね。

 

ただし、名前をつけたとしても後で使用することはできません。
次のようなエラーがでます。

 

JavaScript

sokuzikansu();
// 結果:ReferenceError: sokuzikansu is not defined

 

例外的に関数内部で、自分自身の名前で呼び出すことができます。

 

JavaScript

let 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(){} の部分だけです。
さらにこの部分に関しては、すぐに呼び出されるかどうかはプログラマーの頭の中にしかありません。
うしろの丸カッコが足されて、ようやく「すぐに呼び出される」のです。

 

つまり、”(function( ){ })( );” は、「関数を定義した後すぐに呼び出す式」です。
即時関数なんて名前の関数ではありません。

 

はげしくどうでもいい内容です。
即時関数は世間的に定着しているので、そのまま使っていればいい話です。

 

こんなこと考えているプログラマーは、仕事ができなそうですね!!

 

 

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://note.affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ