好程序員前端教程之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');

}());
上面這兩種都是典型的立即執行函數寫法,兩者的區分就是一個執行在匿名函數括號外面,另外一個發起執行的括號在匿名函數裏面。比較常見的是第一種寫法,括號在匿名函數的括號外面。
步驟分解:
1.首先聲明一個匿名函數 function(){alert('我是匿名函數')}。
2.然後在匿名函數後面接一對括號 (),調用這個匿名函數。
那爲什麼還要用一個括號包起來呢?其實是爲了兼容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的第三個參數,同樣可以得到跟使用立即執行函數同樣的效果。
所以說匿名函數和閉包之間沒有什麼關係,只不過很多時候在用到匿名函數解決問題的時候恰好形成了一個閉包,就導致很多人分不清楚匿名函數和閉包的關係。

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