前言
- 閉包是什么
- 閉包原理是什么
- 閉包優缺點
- 閉包應用場景
步驟
1)什么是閉包
函數執行后返回結果是一個內部函數,並被外部變量所引用,如果內部函數持有被執行函數作用域的變量,即形成了閉包。
可以在內部函數訪問到外部函數作用域。使用閉包,一可以讀取函數中的變量,二可以將函數中的變量存儲在內存中,保護變量不被污染。而正因閉包會把函數中的變量值存儲在內存中,會對內存有消耗,所以不能濫用閉包,否則會影響網頁性能,造成內存泄漏。當不需要使用閉包時,要及時釋放內存,可將內層函數對象的變量賦值為null。
可以在另一個作用域中調用一個函數的內部函數並訪問到該函數的作用域中的成員
示例:
function makeFn() {
let msg = 'hello fn'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn()
// fn() 調用了makeFn內部的內部函數function並訪問到了makeFn作用域中的成員msg,這就形成了閉包
2)閉包原理
函數執行分成兩個階段(預編譯階段和執行階段)。
- 在預編譯階段,如果發現內部函數使用了外部函數的變量,則會在內存中創建一個“閉包”對象並保存對應變量值,如果已存在“閉包”,則只需要增加對應屬性值即可。
- 執行完后,函數執行上下文會被銷毀,函數對“閉包”對象的引用也會被銷毀,但其內部函數還持用該“閉包”的引用,所以內部函數可以繼續使用“外部函數”中的變量
利用了函數作用域鏈的特性,一個函數內部定義的函數會將包含外部函數的活動對象添加到它的作用域鏈中,函數執行完畢,其執行作用域鏈銷毀,但因內部函數的作用域鏈仍然在引用這個活動對象,所以其活動對象不會被銷毀,直到內部函數被燒毀后才被銷毀。
3)優點
- 可以從內部函數訪問外部函數的作用域中的變量,且訪問到的變量長期駐扎在內存中,可供之后使用
- 避免變量污染全局
- 把變量存到獨立的作用域,作為私有成員存在
4)缺點
- 對內存消耗有負面影響。因內部函數保存了對外部變量的引用,導致無法被垃圾回收,增大內存使用量,所以使用不當會導致內存泄漏
- 對處理速度具有負面影響。閉包的層級決定了引用的外部變量在查找時經過的作用域鏈長度
- 可能獲取到意外的值(captured value)
4)應用場景
應用場景一: 典型應用是模塊封裝,在各模塊規范出現之前,都是用這樣的方式防止變量污染全局。
var Yideng = (function () {
// 這樣聲明為模塊私有變量,外界無法直接訪問
var foo = 0;
function Yideng() {}
Yideng.prototype.bar = function bar() {
return foo;
};
return Yideng;
}());
應用場景二: 在循環中創建閉包,防止取到意外的值。
如下代碼,無論哪個元素觸發事件,都會彈出 3。因為函數執行后引用的 i 是同一個,而 i 在循環結束后就是 3
for (var i = 0; i < 3; i++) {
document.getElementById('id' + i).onfocus = function() {
alert(i);
};
}
//可用閉包解決
function makeCallback(num) {
return function() {
alert(num);
};
}
for (var i = 0; i < 3; i++) {
document.getElementById('id' + i).onfocus = makeCallback(i);
}