進擊的 JavaScript(五) 之 立即執行函數與閉包

原文鏈接:周大俠啊 進擊的 JavaScript(五) 之 立即執行函數與閉包

前面的閉包中,提到與閉包相似的立即執行函數,感覺兩者還是比較容易弄混吧,嚴格來說(因爲犀牛書和高程對閉包的定義不同),立即執行函數並不屬於閉包,它不滿足閉包的三個條件。


一、圓括號運算符

  圓括號運算符也叫分組運算符,它有兩種用法:如果表達式放在圓括號中,作用是求值;如果跟在函數後面,作用是調用函數

  把表達式放在圓括號之中,將返回表達式的值

console.log((1+2)); // 3

  將函數放在圓括號中,會返回函數本身。如果圓括號緊跟在函數的後面,就表示調用函數,即對函數求值

console.log((function testa(){return 666;}));
// function testa(){return 666;}

console.log(function testa(){return 666;}());
// 666

注意:圓括號運算符不能爲空,否則會報錯

();//SyntaxError: Unexpected token )

由於圓括號的作用是求值,如果將語句放在圓括號之中,就會報錯,因爲語句沒有返回值

(var a = function(){return 666});
// SyntaxError: Unexpected token var


二、函數聲明

使用 function 關鍵字創建一個函數,並且後面帶有函數名,叫函數聲明。

function testa(){}


三、匿名函數

那麼使用 function 關鍵字創建的函數不帶函數名呢? 那就是匿名函數了。

function (){}


四、函數表達式

那麼把匿名函數賦值給一個變量呢?那就是函數表達式了。

var testa = function (){}

其實呢,函數表達式的根本所在,就是阻止了js引擎把 function創建的函數 當作函數聲明來解析。下面再詳說。


五、立即執行函數(IIFE)

那麼立即執行函數呢?

function定義函數之後,立即調用該函數。這種函數就叫做立即執行函數,全稱爲立即調用的函數表達式IIFE(Imdiately Invoked Function Expression)

1、在本系列進擊的 JavaScript(三)中到過,代碼執行時,會先對函數聲明的函數 進行解析(函數聲明提升),而函數表達式,當逐行執行到它時,纔會解析。

2、正因爲函數聲明的提升,導致函數聲明不能立即執行。因爲,函數聲明時,js只會解析到大括號(})就結束了,如果後面有(),只是一個圓括號運算符。

function testa(){
    console.log("testa")
}("666")

//"666"
//如果後面是一個空的圓括號,會報錯,上面提到過。

所以,不知道,你有沒有發現,函數聲明的函數,後面可以不用分號(;)分隔,也可以正常執行,而函數表達式的後面就必須加分號,不然會報錯。你可以自己寫個小栗子驗證下。

3、匿名函數是不能單獨寫的,所以就提不上立即執行了。

function (){}
//Uncaught SyntaxError: Unexpected token (

單獨寫匿名函數,是會報錯的,js引擎 會把它當作函數聲明來解析,而函數聲明就必須要有個函數名,所以會報錯。
所以,你通常看到使用匿名函數,都是當作參數傳遞的,或者把匿名函數轉爲函數表達式。

4、因此,只有函數表達式可以立即執行

var testa = function (){
    console.log("testa")
}()
//"testa"

上面提到過,函數表達式,就是阻止了js引擎把 |用function創建的函數| 當作函數聲明來解析。

注:javascript引擎規定,如果function關鍵字出現在行首,一律解釋成函數聲明語句。

所以,解決方法就是不要讓function出現在行首,讓引擎將其理解成一個表達式。

//常用的兩種,使用圓括號運算符
(function(){console.log("666")})()
(function(){console.log("666")}())

//一元運算符寫法
!function(){console.log("666")}()
+function(){console.log("666")}()
-function(){console.log("666")}()
~function(){console.log("666")}()

都是都可以把函數聲明 轉爲 函數表達式,也就是阻止了把其當作函數聲明解析。


六、立即執行函數在閉包中的應用

1、立即執行函數能配合閉包保存狀態。

來看下 上節內容中閉包的例子:

function makeClosures(i){    
    var i = i;  
    return function(){
        console.log(i);     
    }
}

for (var i=1; i<=5; i++) {
    setTimeout(makeClosures(i),i*1000);  
}
//1
//2
//3
//4
//5

現在,我們來利用立即執行函數來簡化它:

for (var i=1; i<=5; i++) {
    setTimeout((function(i){
        return function(){
            console.log(i);
        }
    })(i),i*1000);
}

第一個匿名函數執行完畢後,返回了第二個匿名函數。第二個匿名函數被當做setTimeout 的第一個參數傳入進去。因爲 setTimeout函數執行了5次,所以立即執行函數裏每次都會返回了一個沒有被執行的匿名函數,(這裏就是返回了5個匿名函數),每個匿名函數內部保存着每次傳進來的i值,因此,每個i 都是不一樣的,所以,就得到了想要的結果

2、立即執行函數配合閉包 模塊化中應用

(function(){
    var meg = "hello zdx";
    
    function say(arg){
        arg = arg || meg;
        console.log(arg)
    }
    
    window.say = say;
})(window)

window.say();

//"hello zdx"

首先立即執行函數,它是個匿名函數,你是得不到它的函數引用,這樣,就避免了全局變量污染。其次,由於函數作用域的規則,在匿名函數外部是訪問不了函數內的變量,函數等的。所以,也經常用立即執行函數模擬塊級作用域。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章