MDN 對閉包的定義爲:bash
閉包是指那些可以訪問自由變量的函數。閉包
什麼又是自由變量呢?異步
自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量。函數
舉個例子:post
var a = 0; //自由變量
function foo() {
console.log(a);//訪問自由變量,此時這個變量並非函數參數或者函數的局部變量
}
foo();
複製代碼
foo 函數能夠訪問變量 a,可是 a 既不是 foo 函數的局部變量,也不是 foo 函數的參數,因此咱們說 a 就是自由變量,那麼函數 foo 就造成了一個閉包。ui
因此在《 JavaScript權威指南 》中講到:從技術的角度講,全部的 JavaScript 函數都是閉包。spa
在ECMAScript中,閉包指的是:線程
接下來就來說講實踐上的閉包。code
如下代碼爲何與預想的輸出不符?隊列
// 代碼1
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 輸出5次5
}, 0)
}
複製代碼
假設A:由於 setTimeout
這塊的任務直接進入了事件隊列中,因此 i
循環以後i先變成了5,再執行 setTimeout
, setTimeout
中的箭頭函數會保存對i的引用,因此會打印5個5.
// 代碼2
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 輸出 0,1,2,3,4
}, 0)
}
複製代碼
假設結論 A 成立,那麼上式應該也是輸出5次5,可是很明顯不是,因此結論A並不徹底正確。
那咱們去掉循環,先寫成最簡單的異步代碼:
function test(a){
setTimeout(function timer(){
console.log(a)
},0)
}
test('hello')
複製代碼
複製代碼執行 test
,setTimeout
將 timer
函數放入了事件隊列,timer
保留着 test
函數的做用域(在函數定義時建立的),test
執行完畢,主線程上沒有其餘任務了,timer
從事件隊列中出隊,執行 timer
,執行 console.log ( a )
,因爲閉包的緣由,a 依然會保留着以前的引用,輸出 'hello'
。
那咱們在回到題目中,由於兩段代碼中的不一樣只有聲明語句,因此咱們提出假設B
:由於在代碼1
中,匿名函數保留着外部詞法做用域,i
都是在全局做用域上,代碼2
中因爲存在塊做用域,因此它保留着每次循環時i的引用。
// 代碼3
for (var i = 0; i < 5; i++) {
((i) => {
setTimeout(function timer() {
console.log(i) // 輸出 0,1,2,3,4
}, 0)
})(i)
}
複製代碼
複製代碼使用 IIFE
傳遞了變量i給匿名函數,IIFE
產生了一個新做用域,timer
中保留對匿名函數中的i的引用,因此會依次輸出。
// 代碼4
for (var i = 0; i < 5; i++) {
(() => {
setTimeout(function timer() {
console.log(i) // 輸出 5個5
}, 0)
})()
}
複製代碼
和代碼3
的區別爲IIFE
沒有給匿名函數傳遞 i,timer
保留的做用域鏈中對i的引用仍是在全局做用域上。
通過以上兩個變體的驗證,因此假設B
成立,即:因爲做用域鏈的變化,閉包中保留的參數引用也發生了變化,輸出的參數也發生了變化。
下例,循環中的每一個迭代器在運行時都會給本身捕獲一個i
的副本,可是根據做用域的工做原理,儘管循環中的五個函數分別是在各個迭代器中分別定義的,可是它們都會被封閉在一個共享的全局做用域中,實際上只有一個i
,換句話說,i
的值在傳入內部函數以前,已經爲 6 了,因此結果每次都會輸出 6 。
for(var i=1; i <= 5; i++){
setTimeout(function(){
console.log(i);//6
},0)
}
複製代碼
解決上面的問題,在每一個循環迭代中都須要一個閉包做用域,下面示例,循環中的每一個迭代器都會生成一個新的做用域。
for(var i=1; i <= 5; i++){
(function(j){
setTimeout(function(){
console.log(j);
})
},0)(i)
}
複製代碼
也可使用let
解決,let
聲明,能夠用來劫持塊做用域,而且在這個塊做用域中生明一個變量。
for(let i=1; i <= 5; i++){
setTimeout(function(){
console.log(i);
},0)
}
複製代碼
簡單的說:函數 + 自由變量就造成了閉包。其實並非特別複雜,只是咱們須要在引用自由變量的時候當心做用域的變化。
新手寫做,若是有錯誤或者不嚴謹的地方,請大夥給予指正。若是這片文章對你有所幫助或者有所啓發,還請給一個贊,鼓勵一下做者,在此謝過。