JavaScriptの即時呼び出し関数式(IIFE)の理解

25391 ワード

まず、これはjsの関数呼び出しの書き方であり、即時実行関数式(IIFE、すなわちimmediately-invoked function expression)と呼ばれる.その名の通りIIFEはあなたの関数をすぐに実行することができます(くだらない話).
一般的にIIFEには以下のような用途があります.
  1. 一度だけ使用する関数を作成し、すぐに実行します.  2. 閉パッケージを作成し、ステータスを保存し、役割ドメインを分離します.  3. 独立したモジュールとして存在し(例えばjQuery)、ネーミング競合を防止し、ネーミング空間注入(モジュールデカップリング)を行う.
1.一度だけ使用する関数を作成し、すぐに実行します.
一度だけ使用する関数を作成するのは理解しやすいが、関数を呼び出す必要がある場所でIIFEを使用するのは、インラインの効果に似ている.
1 (function(){
2     var a = 1, b = 2;
3     console.log(a+b); // 3
4 })();

パラメータを入力することもできます.
1 (function(c){
2     var a = 1, b = 2;
3     console.log(a+b+c); // 6
4 })(3);

IIFEの一般的な形式は匿名関数ですが、名前付き関数であってもかまいません.
1 (function adder(a, b){
2     console.log(a+b); // 7
3 })(3, 4);

jsでは、匿名関数はスタック追跡時にいくつかの不便をもたらすため、名前付き関数をできるだけ使用する必要があります.
 
2.閉パッケージの作成、ステータスの保存、役割ドメインの分離
分離作用域は複雑である.ES 6以前、JSにはブロックレベル作用域がなく、関数作用域のみであった.ブロックレベル作用域のシミュレーションとしてはfunctionでしか作用域をシミュレートできなかった.例えば、以下のコードである.
 1 var myBomb = (function(){
 2     var bomb = "Atomic Bomb"
 3     return {
 4         get: function(){
 5             return bomb
 6         },
 7         set: function(val){
 8             bomb = val
 9         },
10     }
11 })()
12 
13 console.log(myBomb.get()) // Atomic Bomb
14 myBomb.set("h-bomb")
15 console.log(myBomb.get()) // h-bomb
16 
17 console.log(bomb) // ReferenceError: bomb is not defined
18 bomb = "none"
19 console.log(bomb) // none   

通常、関数の実行が完了すると、内部で宣言された変数は破棄されますが、変数bombはmyBombを通過することができます.getとmyBombsetは読み書きしますが、外部から直接読み書きしてはいけません.これは閉包による典型的な効果です.
クローズド・パッケージが何なのかを明確に説明するには、Javascriptクローズド・パッケージ(Closure)を学ぶ文章があります.上のコードはクローズド・パッケージに使われています.すべての閉パケットには、エクスポート方法によって関数の外部から関数の内部変数の値を変更できるという特徴があります.そのため、この特徴を利用して役割ドメインを分離し、「プライベート」の効果をシミュレートすることができます.
IIFEが変数を保存する例を挙げると、3つのファイルを書き込み、まず1つのコンテンツ配列を定義し、その後、forループでこの配列を遍歴してファイルを書き込み、最後にforループの下のラベルで「File i is written.」:
 1 var fs = require('fs');
 2 
 3 var fileContents = ["text1", "text2", "text3"];
 4 for (var i = 0; i < fileContents.length; i++) {
 5     fs.writeFile("file"+i+".txt", fileContents[i], function(err){
 6         if (err) {
 7             console.log(err)
 8         }
 9         console.log("File " + i + " is written.")
10     })
11 }    

このコードの結果は次のとおりです.
File 3 is written. File 3 is written. File 3 is written.
明らかに私たちの意思に反して、「File 3 is written.」を3回印刷しました.各ファイルのインデックスを1回印刷することを望んでいます.
なぜなら、ファイルを書くのは非同期操作であり、ファイルを書き終わってコールバック関数を呼び出すと、forループが完了し、i=3になるからです.この問題を解決するには、IIFEを使用します.
 1 var fs = require('fs');
 2 
 3 var fileContents = ["text1", "text2", "text3"];
 4 for (var i = 0; i < fileContents.length; i++) {
 5     (function(index){
 6         var fileIndex = index;
 7         fs.writeFile("file"+fileIndex+".txt", fileContents[fileIndex], function(err){
 8             if (err) {
 9                 console.log(err)
10             }
11             console.log("File " + fileIndex + " is written.")
12         })
13     })(i)
14 }

今回の結果は正しいです(順序ではありませんが、これは私たちの考慮の範囲内ではありません):
File 1 is written. File 2 is written. File 0 is written.
ここでIIFEで変数キャプチャをしたり、保存したりすることがわかります.
さらにmyBombの例に戻ると、Moduleモードというモードが使用され、多くのjsモジュールがこのように書かれており、IIFEでいくつかのプライベート変数またはプライベート関数を定義し、returnのときに(一般的にはObjectでエクスポートする)外部に露出する必要がある方法をエクスポートします.また,IIFEで定義された変数や関数もグローバルな役割ドメインを汚染せず,それらはいずれも統一されたエントリを介してアクセスする.3.独立モジュールとして存在し、ネーミング競合を防止し、ネーミングスペース注入(モジュールデカップリング)
nsというネーミングスペースに変数とメソッドを注入するには、次のコードを使用します.
 1 var ns = ns || {};
 2 
 3 (function (ns){
 4     ns.name = 'Tom';
 5     ns.greet = function(){
 6     console.log('hello!');
 7 }
 8 })(ns);
 9 
10 console.log(ns); // { name: 'Tom', greet: [Function] }

さらに多くの用途に拡張できます.
 1 (function (ns, undefined){
 2     var salary = 5000; //     
 3     ns.name = 'Tom'; //     
 4     ns.greet = function(){ //     
 5         console.log('hello!');
 6     }
 7 
 8     ns.externalEcho = function(msg){
 9         console.log('external echo: ' + msg);
10         insideEcho(msg);
11     }
12 
13     function insideEcho(msg){ //     
14         console.log('inside echo: ' + msg);
15     }
16 })(window.ns = window.ns || {});
17 
18 console.log(ns.name); // Tom
19 ns.greet(); // hello
20 ns.age = 25;
21 console.log(ns.age); // 25
22 console.log(ns.salary); // undefined
23 ns.externalEcho('JavaScript'); // external echo: JavaScript/inside echo: JavaScript
24 insideEcho('JavaScript'); // Uncaught ReferenceError: insideEcho is not defined
25 ns.insideEcho('JavaScript'); // Uncaught TypeError: ns.insideEcho is not a function

ここで、ネーミングスペースは、関数の外側のコンテキストを書き換えることなく、ローカルに変更され、ネーミング競合を防止する役割を果たすことができます.
注(興味がなければ直接無視できます):上記のIIFEの2番目のパラメータundefinedも説明する必要があります.jsでは、undefinedは値の空きを表し、キーワードではない事前定義されたグローバル変数です.
1 console.log(typeof a); // undefined
2 var a;
3 console.log(a); // undefined

undefinedには複数の意味があり、1つ目はundefinedというデータ型であり、もう1つはundefinedというデータ型の唯一の値undefinedを表す.jsコードで見られるundefinedは一般的にグローバルオブジェクトの属性であり、この属性の初期値はundefinedであり、もう1つの場合、このundefinedは局所変数であり、通常の変数と同様に、その値はundefinedであってもよいし、他のものであってもよい.
ECMAScript 3ではundefinedは可変です.これはundefinedに値を割り当てることができることを意味しますが、ECMAScript 5規格では、グローバルなundefinedは変更できません.
1 console.log(window.undefined); // undefined
2 window.undefined = 1;
3 console.log(window.undefined); // undefined

厳格なモードでは、直接エラーが発生します.
1 'use strict'
2 
3 console.log(window.undefined); // undefined
4 window.undefined = 1;
5 console.log(window.undefined); // Uncaught TypeError: Cannot assign to read only property 'undefined' of object '#'

この局所的なundefinedを保護する必要があります
1 (function (window, document, undefined) { 
2     // ... 
3 })(window, document);

この場合undefinedに値を付ける人がいても問題ありません.
1 undefined = true; 
2 (function (window, document, undefined) { 
3     // undefined          undefined   
4 })(window, document);

しかし、ECMAScript 5の普及(現在ではECMAScript 5をサポートしていないブラウザはほとんどない)に伴い、このような懸念はほとんど必要なくなり、jQueryも最大限の互換性のためにやっています.
以上の例では、名前空間をパラメータとしてIIFEに渡すことで、拡張と装飾を行うことができます.
 1 (function (ns, undefined){
 2     var salary = 5000; //     
 3     ns.name = 'Tom'; //     
 4     ns.greet = function(){ //     
 5         console.log('hello!');
 6     }
 7 
 8     ns.externalEcho = function(msg){
 9         console.log('external echo: ' + msg);
10         insideEcho(msg);
11     }
12 
13     function insideEcho(msg){
14         console.log('inside echo: ' + msg);
15     }    
16 })(window.ns = window.ns || {});
17 
18 (function (ns, undefined){
19     ns.talk = function(){
20         console.log(ns.name + ' says hello.');
21         console.log(ns.name + ' says goodbye.');
22         //             insideEcho,     ,  talk insideEcho        
23     }
24 })(window.ns = window.ns || {});
25 
26 ns.talk(); // Tom says hello. Tom says goodbye.

ネーミングスペースインジェクション
ネーミングスペース注入はIIFEがネーミングスペースの装飾器と拡張器としての変体であり、より汎用性を有する.機能は、IIFE(ここでは関数パッケージと理解できる)内部で特定のネーミング空間に変数/属性と方法を注入し、内部でthisを使用してネーミング空間を指すことです.
 1 var app = app || {};
 2 app.view = {};
 3 
 4 (function (){
 5     var name = 'main';
 6     this.getName = function(){
 7         return name;
 8     }
 9     this.setName = function(newName){
10         name = newName;
11     }
12     this.tabs = {};
13 }).apply(app.view);
14 
15 
16 (function (){
17     var selectedIndex = 0;
18     this.getSelectedIndex = function(){
19         return selectedIndex;
20     }
21     this.setSelectedIndex = function(index){
22         selectedIndex = index;
23     }
24 }).apply(app.view.tabs);
25 
26 console.log(app.view.getName()); // main
27 console.log(app.view.tabs.getSelectedIndex()); // 0
28 app.view.tabs.setSelectedIndex(1); 
29 console.log(app.view.tabs.getSelectedIndex()); // 1

モジュールを一括生産するためにモジュールコンストラクタを書くこともできます.
 1 var ns1 = ns1 || {}, ns2 = ns2 || {};
 2 
 3 var creator = function(val){
 4     var val = val || 0;
 5     this.getVal = function(){
 6         return val;    
 7     }
 8     this.increase = function(){
 9         val += 1;
10     }
11     this.reduce = function(){
12         val -= 1;
13     }
14     this.reset = function(){
15         val = 0;
16     }
17 }
18 
19 creator.call(ns1);
20 creator.call(ns2, 100);
21 console.log(ns1.getVal()); // 0
22 ns1.increase();
23 console.log(ns1.getVal()); // 1
24 console.log(ns2.getVal()); // 100

ある私有変数に対して、APIの形式でそれを読み書きして、これは実はOOPのいくつかの思想のjsの応用です.
転載先:https://www.cnblogs.com/nullcc/p/5827064.html