天天看點

好程式員前端教程之JavaScript閉包和匿名函數的關系詳解

好程式員前端教程之JavaScript閉包和匿名函數的關系詳解

本文講的是關于JavaScript閉包和匿名函數兩者之間的關系,從匿名函數概念到立即執行函數,最後到閉包。下面一起來看看文章分析,希望你會喜歡。

前面講了一篇在for循環中加setTimeout輸出内容,我們用到了一個閉包,但同時也可以說是匿名函數,到底匿名函數和閉包有沒有關系呢?【答案是它們之間沒有關系】

匿名函數

匿名函數,顧名思義,就是沒有名字的函數,與之對應的就是有名字的函數,也叫具名函數。

//匿名函數

function (){

console.log('匿名函數');           

}

//具名函數

function myFn(){

console.log('具名函數');           

//變量a就是匿名函數的名字

var a = function(){

console.log('a就是匿名函數的名字');           

如果我們直接在控制台中運作匿名函數,會發現報錯,無法執行。匿名函數是無法執行的,一般用到匿名函數的時候都是立即執行,也叫自執行匿名函數或者自調用匿名函數,一般人都叫立即執行函數。

立即執行函數

比較常見的立即執行函數如下:

;(function(){

console.log('caibaojian.com');           

})()

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了。

那怎麼解決呢?用立即執行函數給每個定時器創造一個獨立作用域即可。

(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 inner(){
    console.log(i);
}
return inner;           

var outer = box(1);

outer();

結論

很明顯這是一個閉包,然後我們再看看我們最前面的匿名函數代碼和立即執行函數代碼,可以看出匿名函數和閉包兩者并沒有關系。閉包既可以在匿名函數也可以在具名函數中使用。

這個for循環中的閉包怎麼了解以及自執行匿名函數的作用:

這個for循環産生的閉包其實是定時器的回調函數,這些回調函數的執行環境是window,類似剛才例子中的引用inner的全局outer的執行環境,匿名函數則相當于剛才例子中的box函數。

Stackoverflow網站上的一個提問跟我們今天分析的類似。有一個回答挺好。

閉包機制适用于所有JavaScript函數,無論是否匿名。

我認為這兩個概念之間的混淆來自于使用術語“閉包”,其中作者已經說過“下面的代碼建立一個閉包”,然後給出了一個恰好使用匿名函數的例子。 在這種情況下,閉包機制通常是使特定代碼段按預期工作的重要因素,而使用匿名函數而不是命名函數恰好是編碼它的便捷方式。 閱讀這些例子并且第一次看到“閉包”的人然後誤解了這個術語,并繼續在他們自己的Stack Overflow或部落格文章中錯誤地使用它,是以混亂傳播。

一開始我以為匿名函數跟閉包有關系,那是因為恰好這個定時器使用了閉包和匿名函數,讓我們誤認為兩者之間有關系,其實還有很多種方法可以解決這個問題,比如我們之前說到的setTimeout的第三個參數,同樣可以得到跟使用立即執行函數同樣的效果。

是以說匿名函數和閉包之間沒有什麼關系,隻不過很多時候在用到匿名函數解決問題的時候恰好形成了一個閉包,就導緻很多人分不清楚匿名函數和閉包的關系。

繼續閱讀