原文鏈接:周大俠啊 進擊的 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"
首先立即執行函數,它是個匿名函數,你是得不到它的函數引用,這樣,就避免了全局變量污染。其次,由於函數作用域的規則,在匿名函數外部是訪問不了函數內的變量,函數等的。所以,也經常用立即執行函數模擬塊級作用域。