詳解javascript立即執行函數表達式(IIFE)


立即執行函數,就是在定義函數的時候直接執行,這里不是申明函數而是一個函數表達式

1.問題

在javascript中,每一個函數在被調用的時候都會創建一個執行上下文,在函數內部定義的變量和函數只能在該函數內部調用,正是因為這個上下文,使得在調用函數的時候可以創建一些私有變量。如下代碼

    //makeCounter,返回一個新的函數(閉包),這個函數可以訪問makeCounter里的局部變量i
    function makeCounter() {
      var i = 0;
      return function () {
        document.write(++i);
        document.write('<br>');
      }
    }

    //counter1和counter2是不同的實例,分別擁有自己范圍內的變量i
    var counter1 = makeCounter();
    counter1();
    counter1();

    var counter2 = makeCounter();
    counter2();
    counter2();

這里i是函數makeCounter函數內的局部變量,所以定義的counter1和counter2都有自己的變量i,上面代碼輸出結果如下:

注意閉i始終保存在內存中,所以第二次調用的時候輸出的是2。

普通情況下我們定義一個函數,然后在語句中函數名字后面加上一對圓括號就可以直接調用它,能不能定義完之后直接在后面加上小括號調用呢?如下

function(){ counter1(); }(); // SyntaxError: Unexpected token (

答案是不行,這樣會報錯的。為什么呢?在javascript解釋代碼的時候,遇到function關鍵字的時候就認為這里是一個函數聲明而不是函數表達式,如果沒有顯式地定義成函數表達式就會報錯,因為函數聲明需要一個函數名,上面的代碼沒有函數名。

既然是因為沒有函數名字報錯那好就加上一個函數名,如下:

function foo(){ counter1(); }(); // SyntaxError: Unexpected token )

依然會報錯,為什么呢?在一個函數聲明語句(這次是正確的)后面加上一對圓括號,這對圓括號和前面的聲明語句沒有任關系,而只是一個分組操作符,用來控制運算的優先級,這里的意思是小括號里面優先計算,所以上面代碼等同於:

function foo(){ counter1(); }
(); // SyntaxError: Unexpected token )

 

2.概念

正確的寫法是怎樣的呢?簡單,如下:

(function () { counter1(); }());

這樣為什么就可以呢?在javascript里圓括號內不能包含語句,當解釋器對代碼進行解釋的時候遇到圓括號就認為這里面是表達式,然后遇到function關鍵字就認為這是一個函數表達式,而不是函數聲明。而更加奇妙的是只要是能將后面語句預先解釋為表達式都可以,不一定是分分組操作符,於是立即執行函數表達式有了五花八門的寫法,如下:

    (function () { counter1(); }());
    (function () { counter1(); })();
    var i = function(){ counter1(); }();
    true && function () { counter1(); }();
    0, function(){ counter1() }();
    !function () { counter1(); }();
    ~function () { counter1(); }();
    -function () { counter1(); }();
    +function () { counter1(); }();

輸出結果如下:

甚至可以這樣:

    new function(){ counter1(); }
    new function(){ counter1(); }() // 帶參數

這樣:

var i = function(){ counter1(); }();
var j = (function(){ return 10; }());

這是為什么呢?因為new,=是運算符,和+,-,*,/一個樣,都會把后面的語句預先解釋為表達式。這里推薦上面一種寫法,因為function內部代碼如果太多,我們不得不滾到最后去看function(){}后是否帶有()。

 

3.立即執行函數和閉包有什么關系

 和普通函數傳參一樣,立即執行函數也可以傳遞參數。如果在函數內部定一個函數,而里面的那個函數能引用外部的變量和參數(閉包),我們就能用立即執行函數鎖定變量保存狀態。

我們在hmtl頁面中方兩個超鏈接標簽,然后用下面的代碼來測試:

<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是塊級作用域內的變量,是es6新定義的,這里不展開。

4.模塊模式

立即執行函數在模塊化的時候也有用,用立即執行函數處理模塊可以減少全局變量造成的空間污染,而是使用私有變量。

如下創建一個立即執行的匿名函數,該函數返回一個對象,包含要暴露給外部的屬性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;
        }
      };
    }());
    document.write('<br>');
    document.write(counter.get());document.write('<br>');
    document.write(counter.set( 3 ));document.write('<br>');
    document.write(counter.increment());document.write('<br>'); // 4
    document.write(counter.increment());document.write('<br>'); // 5

注意,這里如果使用counter.i來訪問這個內部變量,會報錯undefined,因為i並不是counter的屬性。

好了,就這么多。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM