閉包
閉包是js一個非常重要但是理解起來又有一定難度的概念,理解閉包能讓你的js功力得到一個質變。
閉包的概念
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。
JavaScript中的函數會形成閉包。 閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量。
function foo() {
var a = "哈嘍"
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() // 哈嘍
// 這就是閉包的效果!!!!!!
函數 bar() 的詞法作用域能夠訪問 foo() 的內部作用域。這個例子bar()在自己定義的詞法作用域以外的地方被正常執行。這就是閉包的威力。
這個函數在定義時的詞法作用域以外的地方被調用。閉包使得函數可以繼續訪問定義時的詞法作用域。
在 foo() 執行後,通常會期待 foo() 的整個內部作用域都被銷燬,因爲我們知道引擎有垃圾回收器用來釋放不再使用的內存空間。由於看上去 foo() 的內容不會再被使用,所以很自然地會考慮對其進行回收。
而閉包的“神奇”之處正是可以阻止這件事情的發生。事實上內部作用域依然存在,因此沒有被回收。是因爲 bar() 在使用這個內部作用域!
拜 bar() 所聲明的位置所賜,它擁有涵蓋 foo() 內部作用域的閉包,使得該作用域能夠一直存活,以供 bar() 在之後任何時間進行引用。
bar() 依然持有對該作用域的引用,而這個引用就叫作閉包
function foo() {
var a = 2;
function baz() {
console.log(a);
}
bar(baz)
}
function bar(fn) {
fn(); // 2 這就是閉包的威力
}
無論通過何種手段將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。
循環中的閉包
for (var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}
// 6
// 6
// 6
// 6
// 6
全部會輸出6, 因爲所有函數共享一個 i 的引用
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, i*1000)
})(i)
}
// 1
// 2
// 3
// 4
// 5
我們可以用es6的let使用塊作用域來解決這個問題
for (let i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000)
}
// 1
// 2
// 3
// 4
// 5
for 循環頭部的 let 聲明還會有一 個特殊的行爲。這個行爲指出變量在循環過程中不止被聲明一次,每次迭代都會聲明。隨 後的每個迭代都會使用上一個迭代結束時的值來初始化這個變量。
小結
當函數可以記住並訪問所在的詞法作用域,即使函數是在當前詞法作用域之外執行,這時 就產生了閉包。