1. JS中變量的作用域
在理解閉包之前,我們得弄清楚JS中變量的作用域原理,它分為全局作用域和局部作用域,它有一個特點就是局部可以獲取全局的聲明變量,而全局卻不能得到局部聲明的變量,我們先來看一個小例子:
var num = 1902;
function methods() {
var qty = 25;
console.log(num);
}
methods(); // 1902
console.log(qty); // 報錯,找不到 qty 變量;
當然在局部聲明變量的時候一定要用var或者let,不然會在全局生成一個變量,容易照成全局污染,上面代碼如果qty沒有var聲明:
var num = 1902;
function methods() {
qty = 25;
console.log(num);
}
methods(); // 1902;
console.log(qty); // 25;
2.什么是閉包
那么現(xiàn)在問題來了,如果我們非要從外部來讀取局部變量中的聲明變量呢,尋常方式不行,我們可以變通一下,就是在函數(shù)內(nèi)部再嵌套一個函數(shù),然后返回這個嵌套函數(shù):
function methods() {
var qty = 25;
return function num() {
console.log(qty);
}
}
var num1 = methods();
num1() // 25
這樣,控制臺就會打印出 qty 變量的值了,其實在上面的代碼中,被返回的函數(shù) num()就產(chǎn)生了閉包,由于在js中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以可以把閉包理解成定義在一個函數(shù)內(nèi)部的函數(shù),簡單的說,JavaScript允許使用內(nèi)部函數(shù):即函數(shù)定義和函數(shù)表達式位于另一個函數(shù)的函數(shù)體內(nèi)而且,這些內(nèi)部函數(shù)可以訪問它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)和聲明的其他內(nèi)部函數(shù)。當其中一個這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時,就會形成閉包。
3.閉包的用途
我們接著探討閉包的表達形式以及用途。
(1)匿名自執(zhí)行函數(shù)
(function () {
var methods = function () {
console.log('執(zhí)行完函數(shù)后銷毀')
};
methods();
})();
上面代碼也是閉包的應用,運用于函數(shù)只會執(zhí)行一次的場景,執(zhí)行完便會被釋放。
(2)給對象設置私有變量
var result = function () {
var count = 1;
return function () {
count++;
console.log(count)
}
}()
result(); // 2
result(); // 3
result(); // 4
result(); // 5
上面代碼可以保存自己的私有變量,防止代碼之間的沖突。
(3)異步執(zhí)行函數(shù)
下面先看一個小例子:
for (var i = 0; i < 5; i++) {
console.log(i); //0,1,2,3,4
}
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); //5,5,5,5,5
}, 0)
}
為什么會出現(xiàn)上述差異呢,原因在于setTimeout是異步加載,所以為先循環(huán)結(jié)束后輸出最后結(jié)果,如果我們就是想實現(xiàn)輸出0,1,2,3,4呢。那就要用到閉包了:
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(() => {
console.log(i); //0,1,2,3,4
}, 0);
})(i);
}
上面就是異步調(diào)用閉包,它可以讓變量值始終保存在內(nèi)存中,即使外部的執(zhí)行環(huán)境已經(jīng)結(jié)束了。
4.閉包的優(yōu)缺點
(1)由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
(2)閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內(nèi)部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。