好程式設計師前端教程之JavaScript閉包和匿名函式的關係詳解
好程式設計師前端教程之JavaScript閉包和匿名函式的關係詳解
本文講的是關於JavaScript閉包和匿名函式兩者之間的關係,從匿名函式概念到立即執行函式,最後到閉包。下面一起來看看文章分析,希望你會喜歡。
前面講了一篇在for迴圈中加setTimeout輸出內容,我們用到了一個閉包,但同時也可以說是匿名函式,到底匿名函式和閉包有沒有關係呢?【答案是它們之間沒有關係】
匿名函式
匿名函式,顧名思義,就是沒有名字的函式,與之對應的就是有名字的函式,也叫具名函式。
//匿名函式
function (){
console.log('匿名函式');
}
//具名函式
function myFn(){
console.log('具名函式');
}
//變數a就是匿名函式的名字
var a = function(){
console.log('a就是匿名函式的名字');
}
如果我們直接在控制檯中執行匿名函式,會發現報錯,無法執行。匿名函式是無法執行的,一般用到匿名函式的時候都是立即執行,也叫自執行匿名函式或者自呼叫匿名函式,一般人都叫立即執行函式。
立即執行函式
比較常見的立即執行函式如下:
;(function(){
console.log('caibaojian.com');
})()
;(function(){
console.log('caibaojian.com');
}());
上面這兩種都是典型的立即執行函式寫法,兩者的區分就是一個執行在匿名函式括號外面,另外一個發起執行的括號在匿名函式裡面。比較常見的是第一種寫法,括號在匿名函式的括號外面。
步驟分解:
- 首先宣告一個匿名函式 function(){alert('我是匿名函式')}。
- 然後在匿名函式後面接一對括號 (),呼叫這個匿名函式。
那為什麼還要用一個括號包起來呢?其實是為了相容JS的語法,如果我們不加括號,直接寫成
function (){alert('我是匿名函式')}()
瀏覽器會報語法錯誤,想要通過瀏覽器的語法檢查,必須加點小東西,比如下面幾種
(function(){alert('我是匿名函式')} ()) // 用括號把整個表示式包起來
(function(){alert('我是匿名函式')}) () //用括號把函式包起來
!function(){alert('我是匿名函式')}() // 求反,我們不在意值是多少,只想通過語法檢查。
+function(){alert('我是匿名函式')}()
-function(){alert('我是匿名函式')}()
~function(){alert('我是匿名函式')}()
void function(){alert('我是匿名函式')}()
new function(){alert('我是匿名函式')}()
實際上,立即執行函式的作用只有一個:建立一個獨立的作用域,在這個作用域裡面,外面訪問不到,避免變數汙染。比如我們前面的一篇文章,setTimeout的第三個引數裡面講到的一道題目。
for(var i=0;i<6;i++){
setTimeout(function(){
console.log(i); //為什麼輸出的總是 6,而不是0,1,2,3,4,5
},i*1000);
}
我們發現上面這個定時器總是輸出6,因為setTimeout裡面的執行函式是非同步的,執行的時候,i的值是貫穿整個作用域的,而不是單獨一個給每個定期器分配了一個i,for執行完的值是6,此時輸出就總是6了。
那怎麼解決呢?用立即執行函式給每個定時器創造一個獨立作用域即可。
for(var i=0;i<6;i++){
(function(j){
setTimeout(function(){
console.log(j);
},j*1000);
})(i);
}
在for迴圈執行時,立即執行函式就已經有了結果了。而每個立即執行函式裡面的j值就是獨立的一個,不會受後面影響。所以會分別執行5次定時器。
//第一個立即執行函式
(function(0){
setTimeout(function(){
console.log(0);
})
})(0);
//第二個立即執行函式
(function(1){
setTimeout(function(){
console.log(1);
})
})(1);
//……
//第六個立即執行函式
(function(5){
setTimeout(function(){
console.log(5);
})
})(5);
i 的值從 0 變化到 5,對應 6 個立即執行函式,這 6 個立即執行函式裡面的 j 「分別」是 0、1、2、3、4、5。
上面說了這麼多關於匿名函式和立即執行函式的,相信你對這兩個概念已經很清楚,那麼閉包跟匿名函式有關係嗎?
閉包
js閉包是指有權訪問另一個函式作用域中的變數的函式,個人認為js閉包最大的用處就是防止對全域性作用域的汙染。閉包最神奇的地方就是能在一個函式外訪問函式中的區域性變數,把這些變數用閉包的形式放在函式中便能避免汙染。
我們可以分離出上面的第一個立即執行函式
function box(i){
setTimeout(function(){
console.log(i);
},i*1000);
}
box(1);
//或者這樣
function box(i){
function inner(){
console.log(i);
}
return inner;
}
var outer = box(1);
outer();
結論
很明顯這是一個閉包,然後我們再看看我們最前面的匿名函式程式碼和立即執行函式程式碼,可以看出匿名函式和閉包兩者並沒有關係。閉包既可以在匿名函式也可以在具名函式中使用。
這個for迴圈中的閉包怎麼理解以及自執行匿名函式的作用:
這個for迴圈產生的閉包其實是定時器的回撥函式,這些回撥函式的執行環境是window,類似剛才例子中的引用inner的全域性outer的執行環境,匿名函式則相當於剛才例子中的box函式。
Stackoverflow網站上的一個提問跟我們今天分析的類似。有一個回答挺好。
閉包機制適用於所有JavaScript函式,無論是否匿名。
我認為這兩個概念之間的混淆來自於使用術語“閉包”,其中作者已經說過“下面的程式碼建立一個閉包”,然後給出了一個恰好使用匿名函式的例子。 在這種情況下,閉包機制通常是使特定程式碼段按預期工作的重要因素,而使用匿名函式而不是命名函式恰好是編碼它的便捷方式。 閱讀這些例子並且第一次看到“閉包”的人然後誤解了這個術語,並繼續在他們自己的Stack Overflow或部落格文章中錯誤地使用它,因此混亂傳播。
一開始我以為匿名函式跟閉包有關係,那是因為恰好這個定時器使用了閉包和匿名函式,讓我們誤認為兩者之間有關係,其實還有很多種方法可以解決這個問題,比如我們之前說到的setTimeout的第三個引數,同樣可以得到跟使用立即執行函式同樣的效果。
所以說匿名函式和閉包之間沒有什麼關係,只不過很多時候在用到匿名函式解決問題的時候恰好形成了一個閉包,就導致很多人分不清楚匿名函式和閉包的關係。