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

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

1.問題java

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

    //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 )

依然會報錯,爲何呢?在一個函數聲明語句(此次是正確的)後面加上一對圓括號,這對圓括號和前面的聲明語句沒有任關係,而只是一個分組操做符,用來控制運算的優先級,這裏的意思是小括號裏面優先計算,因此上面代碼等同於:spa

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

 

2.概念code

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

(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的屬性。

好了,就這麼多。

相關文章
相關標籤/搜索