JS中的 當即執行函數(IIFE)

在JS中,若是咱們定義了一個函數以下:javascript

function fn(){ /* code */ }
複製代碼

或者java

let fn = function(){ /* code */ }
複製代碼

當咱們在調用時,都須要在後面加上一對圓括號,像這樣:fn()安全

正如上面所寫的那樣,fn相對於函數表達式function(){ /* code */ }只是引用的變量
那麼咱們能夠能夠這樣寫嗎?bash

function(){ /* code */ }()
複製代碼

若是這樣的話,是會報錯的,由於當圓括號爲了調用函數而出如今函數後面時,不管在全局環境或者局部環境裏遇到了這樣的function關鍵字。
默認的,他會將他看成是一個函數聲明,而不是函數表達式,若是你不明確的告訴圓括號他是一個表達式,他會將其看成沒有名字的函數聲明而且拋出一個錯誤,由於函數聲明須要一個名字。閉包

那麼若是咱們加上函數名呢?ide

function fn(){ /* code */ }()
複製代碼

依然報錯,由於這對圓括號和前面的聲明語句沒有任關係,而只是一個分組操做符,用來控制運算的優先級,這裏的意思是小括號裏面優先計算,因此上面代碼等同於:模塊化

function fn(){ /* code */ }
()
複製代碼

當即執行函數(IIFE)

當咱們聲明瞭一個函數,可能不須要調用屢次,而且能夠只調用一次獲得一個單一的值函數

顯然上面的方法是不行的,那麼怎麼辦呢?其實咱們能夠這樣寫:post

(function () { /* code */ }());
複製代碼

或者這樣測試

(function () { /* code */ })();
複製代碼

那麼這二者又有什麼區別呢?

其實這二者形式就是最開始寫的那兩種函數的形式:

  • 函數聲明:function fn(){ /* code */ }
  • 函數表達式:let fn = function(){ /* code */ }

函數表達式後面能夠加括號當即調用該函數,函數聲明不能夠,只能以 fn() 形式調用

因此咱們能夠這樣寫當即執行函數

寫法

(function () { /* code */ }());
(function () { /* code */ })();
let i = function(){ /* code */ }();
let j = (function(){ /* code */ }());
true && function () { /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
new function(){ /* code */ };
new function(){ /* code */ }(); // 帶參數
複製代碼

能夠看到,在 function 前面加 ! 、+ 、- 甚至是逗號等均可以起到當即執行的效果,而 () 、! 、+ 、- 、= 等運算符,都將函數聲明轉換成函數表達式,消除了 javascript 引擎識別函數表達式和函數聲明的歧義,告訴 javascript 引擎這是一個函數表達式,不是函數聲明,能夠在後面加括號,並當即執行函數的代碼。

其實加括號是最安全的作法,由於 ! 、+ 、- 等運算符還會和函數的返回值進行運算,有時形成沒必要要的麻煩。

IIFE 與 閉包

說到當即執行函數的話,順便扯一下閉包

和普通函數傳參同樣,當即執行函數也能夠傳遞參數。若是在函數內部定一個函數,而裏面的那個函數能引用外部的變量和參數(閉包),咱們就能用當即執行函數鎖定變量保存狀態。

下面用代碼來作測試:

<div>
    <ul>
        <li><a>第一個超連接</a></li>
        <li><a>第二個超連接</a></li>
    </ul>
</div>
    var elems = document.getElementsByTagName('a');
    for(var i=0; i<elems.length; i++) {
      elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am click Link ' + i);
      }, 'false')
    }
複製代碼

這段代碼意圖是點擊第一個超連接提示「I am click Link 0」,點擊第二個提示「I am click Link 1」。真的是這樣嗎? 不是,每一次都是「I am click Link 2」

爲何?由於i的值沒有被鎖住,當咱們點擊連接的時候其實for循環早已經執行完了,因而在點擊的時候i的值已是elems.length了。

修改代碼:

var elems = document.getElementsByTagName('a');
    for(var i=0; i < elems.length; i++){
      (function (LockedInIndex) {
        elems[i].addEventListener('click', function (e) {
          e.preventDefault();
          alert('I am cliick Link ' + i);
        }, 'false')
      })(i)
    }
複製代碼

此次能夠正確的輸出結果,i的值被傳給了LockedIndex,而且被鎖定在內存中,儘管for循環以後i的值已經改變,可是當即執行函數內部的LockedIndex的值並不會改變。

固然也能夠這樣寫:

var elems = document.getElementsByTagName('a');
    for ( var i = 0; i < elems.length; i++ ) {
      elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
        return function(e){
          e.preventDefault();
          alert( 'I am link ' + lockedInIndex );
        };
      })( i ), 'false' );
    }
複製代碼

最好的方法就是用let,以下:

var elems = document.getElementsByTagName('a');
    for(let i=0; i<elems.length; i++) {
      elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am click Link ' + i);
      }, 'false')
    }
複製代碼

關於let具體的使用,請參考個人文章:
《JavaScript 變量的使用》
《ES6 新增內容總結》

模塊模式

當即執行函數在模塊化的時候也有用,用當即執行函數處理模塊能夠減小全局變量形成的空間污染,而是使用私有變量。

以下建立一個當即執行的匿名函數,該函數返回一個對象,包含要暴露給外部的屬性i,若是不實用當即執行函數就要多定義一個屬性i了,這個i就會顯示的暴露給外部,這樣:counter.i,這種方式明顯不太安全。

var counter = (function(){
    var i = 0;
    return {
        get: function(){
            return i;
        },
        set: function(val){
            i = val;
        },
        increment: function(){
            return ++i;
        }
    }
    }());
    counter.get();//0
    counter.set(3);
    counter.increment();//4
    counter.increment();//5

    conuter.i;//undefined (`i` is not a property of the returned object)
    i;//ReferenceError: i is not defined (it only exists inside the closure)
複製代碼

這裏若是使用counter.i來訪問這個內部變量,會報錯undefined,由於i並非counter的屬性。

模塊模式方法不只至關的厲害並且簡單。很是少的代碼,你能夠有效的利用與方法和屬性相關的命名,在一個對象裏,組織所有的模塊代碼,即最小化了全局變量的污染也創造了使用變量。

+_+

相關文章
相關標籤/搜索