要理解立即執行函數(function(){})(),先了解些函數的基本概念(函數聲明、函數表達式、匿名函數)。
函數聲明:使用function聲明函數,並指定函數名。
function setFn() {
// coding
}
函數表達式:使用function聲明函數,但未指定函數名,將匿名函數賦予一個變量。
var setFn = function() {
// coding
}
匿名函數:使用function關鍵字聲明函數,但未指定函數名。匿名函數屬於函數表達式,匿名函數有很多作用,賦予一個變量則創建函數,賦予一個事件則成爲事件處理程序或創建閉包等等。
function() {
// coding
}
函數聲明與函數表達式的不同在於:
1. 函數聲明可在當前作用域下提前調用執行,函數表達式需等執行到該函數後,方可執行,不可提前調用。
setFn()
function setFn() {
// coding
}
// 正常,函數聲明可提前調用
setFn()
var setFn = function() {
// coding
}
// 報錯,setFn未保存對函數的引用,函數調用需放在函數表達式後面
2. 函數表達式可直接在函數後加括號調用。
var setFn = function() {
console.log(2)
}()
// 2 解析至此,可直接執行調用
立即執行函數(function(){})()可以看出很像函數表達式的調用,但爲什麼要加括號呢?如果不加括號:
function(){
console.log(1)
}()
// 報錯,函數需要函數名
解析: 雖然匿名函數屬於函數表達式,但未進行賦值,所以javascript解析時將開頭的function當做函數聲明,故報錯提示需要函數名
立即執行函數裏面的函數必須是函數表達式,所以由var setFn = function() {}()可以理解爲在匿名函數前加了 = 運算符後,將函數聲明轉化爲函數表達式,所以拿!,+,-,()...等運算符來測試下是否如此。
!function(){
console.log(1)
}()
// 1
+function(){
console.log(2)
}()
// 2
-function(){
console.log(3)
}()
// 3
(function(){
console.log(4)
})()
// 4
由此可見,加運算符確實可將函數聲明轉化爲函數表達式,而之所以使用括號,是因爲括號相對其他運算符會更安全,可以減少不必要的麻煩。
立即執行函數與正常函數傳參形式是一致的。
(function(a, b){
console.log(a + b);
})(1, 2)
// 3
(function(){}())這樣寫的好處是在內部定義的變量不會跟外部的變量有衝突,達到保護內部變量的作用。jquery等好多庫裏面就好多使用的這種方式,然後再return一個變量,詳情可以查看另一篇博文https://blog.csdn.net/xcymorningsun/article/details/85234232
————————————————————————————————————————————————————
關於保護內部變量閉包問題,這個可以看下面的例子
循環中的閉包
一個常見的錯誤出現在循環中使用閉包,假設我們需要在每次循環中調用循環序號
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
上面的代碼不會輸出數字 0
到 9
,而是會輸出數字 10
十次。
當 console.log
被調用的時候, 匿名函數保持對外部變量 i
的引用,此時 for
循環已經結束, i
的值被修改成了 10
.
爲了得到想要的結果,需要在每次循環中創建變量 i
的 拷貝。
避免引用錯誤
爲了正確的獲得循環序號,最好使用 匿名包裹器( 譯者注:其實就是我們通常說的自執行匿名函數)。
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
外部的匿名函數會立即執行,並把 i
作爲它的參數,此時函數內 e
變量就擁有了 i
的一個拷貝。
當傳遞給 setTimeout
的匿名函數執行時,它就擁有了對 e
的引用,而這個值是 不會被循環改變的。
有另一個方法完成同樣的工作;那就是從匿名包裝器中返回一個函數。這和上面的代碼效果一樣。
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}