好程序員前端教程之JavaScript閉包和匿名函數的關係詳解javascript
本文講的是關於JavaScript閉包和匿名函數二者之間的關係,從匿名函數概念到當即執行函數,最後到閉包。下面一塊兒來看看文章分析,但願你會喜歡。前端
前面講了一篇在for循環中加setTimeout輸出內容,咱們用到了一個閉包,但同時也能夠說是匿名函數,到底匿名函數和閉包有沒有關係呢?【答案是它們之間沒有關係】java
匿名函數,顧名思義,就是沒有名字的函數,與之對應的就是有名字的函數,也叫具名函數。程序員
//匿名函數瀏覽器
function (){閉包
console.log('匿名函數');異步
}函數
//具名函數網站
function myFn(){編碼
console.log('具名函數');
}
//變量a就是匿名函數的名字
var a = function(){
console.log('a就是匿名函數的名字');
}
若是咱們直接在控制檯中運行匿名函數,會發現報錯,沒法執行。匿名函數是沒法執行的,通常用到匿名函數的時候都是當即執行,也叫自執行匿名函數或者自調用匿名函數,通常人都叫當即執行函數。
比較常見的當即執行函數以下:
;(function(){
console.log('caibaojian.com');
})()
;(function(){
console.log('caibaojian.com');
}());
上面這兩種都是典型的當即執行函數寫法,二者的區分就是一個執行在匿名函數括號外面,另一個發起執行的括號在匿名函數裏面。比較常見的是第一種寫法,括號在匿名函數的括號外面。
步驟分解:
那爲何還要用一個括號包起來呢?實際上是爲了兼容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、一、二、三、四、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的第三個參數,一樣能夠獲得跟使用當即執行函數一樣的效果。
因此說匿名函數和閉包之間沒有什麼關係,只不過不少時候在用到匿名函數解決問題的時候剛好造成了一個閉包,就致使不少人分不清楚匿名函數和閉包的關係。