什麼是閉包
從作用上說:既能重用一個變量,又能保護變量不會被全局污染的一種編程方式
閉包的本質/原理
外層函數的作用域對象,被內層函數引用着無法釋放,就形成閉包對象
要理解上面這兩句話首先我們來看一個小例子
<script>
var a = 100;
function fun() {
a = 200;
a++;
console.log(a);
}
fun();
console.log(a);
</script>
這個例子輸出的結果是什麼呢?以上面的例子來分析代碼執行的過程
- 當瀏覽器開始掃描時,這時候還沒有執行代碼。只是先聲明變量,將變量先存儲在window對象中(可打印一下console.log(window)看一看裏面的變量)
代碼執行後,函數會自動創建臨時函數作用域對象
當代碼執行完成後,會把臨時創建的函數作用域對象給清空
所以函數中的a是全局的變量a,所以兩次打印出來的值都是201。那如果把上面的例子改成下面的代碼會怎麼樣呢?
var a = 100;
function fun() {
var a = 200;
a++;
console.log(a);
}
fun();
console.log(a);
這個例子輸出的結果是兩次201嗎?我們來輸出看一看
這爲什麼兩次的輸出不同呢?因爲函數裏面聲明瞭一個a變量,在函數裏面聲明的變量會存在臨時創建的函數作用域對象中,函數內部的變量a會先去臨時創建的函數作用域對象中查找,找到有a變量就拿函數作用域對象的變量來賦值,就不會去到window對象中找變量a,所以window對象中的a變量不受影響。
到代碼執行結束後臨時創建的函數作用域對象就會被回收,所以函數中的答打印是201,函數外的a變量沒有受到影響所以還是100
所以得出結論
1.使用全局變量:可以被重複使用,但是極易被污染和篡改。
2.使用局部變量:只能在函數內使用,出了函數用不了,絕對不會被篡改,但是不能重用。
那我想我的變量不被全局給污染和篡改又能重用應該怎麼辦?
這時候就要用到最上面閉包了,使用閉包就能實現這個需求,那閉包如何使用呢?看下面的例子
<script>
//需求: 定義一個函數爲小孩兒管理壓歲錢
// 小孩兒每花一筆錢,可以從總錢數中扣除花的錢,提示還剩xxx
//1. 定義一個外層函數,包裹要保護的變量和內層函數
function parent() {
var total = 1000;
//2. 外層函數將內層函數對象返回到函數外部,讓外部可用
//不用給內層函數起名字
return function (money) {
//小孩兒 //局部
total -= money;
console.log(`花了${money}, 還剩${total}`);
};
}
//3. 想用內層函數的人需要調用外層函數,才能獲得返回出來的內層函數,保存在變量中,反覆使用。
var pay = parent();
total = 0; //試圖篡改total變量 結果total不能被篡改
pay(100); //花了100, 還剩900
pay(100); //花了100, 還剩800
pay(100); //花了100, 還剩700
var pay1 = parent();
pay1(100); //重新形成一個閉包 花了100, 還剩900
</script>
看看輸出結果
閉包的形成如下圖
在parent函數正在被調用的那瞬間,就會創建一個臨時函數作用域對象
然後調parent後,由於pay一直引用這parent創建的臨時函數作用域,導致這個臨時創建出來的函數作用域無法被釋放,而形成了閉包
總結成一句話
外層函數的作用域對象,被內層函數引用着無法釋放,就形成閉包對象
閉包的缺點
比一般的函數多佔用一塊內存空間——多佔外層函數的作用域對象
如何解決這個缺點呢?以上面的例子來說,如果pay不用到了,就將把pay = null,釋放掉函數作用對象。這樣就不會多佔外層函數的作用域對象
這是我個人對閉包的理解,如果說得不好請多多指教。