網絡上關於做用域及閉包的文章不少,本身對於純理論知識並不能很快的理解,但本身對於圖畫有很強的記憶和理解能力,所以決定將此知識點以圖畫的知識表現出來,加深自身理解的同時若是能幫到正在學習的童鞋就再好不過了git
下面我以函數的整個生命週期來訴說此部分知識github
先寫一下示例代碼數組
var a = 10;
function func(a) {
var a = 20;
a++;
console.log(a);
}
func();
console.log(a);
複製代碼
先建立 ECS,ECS 其實就是專門保存正在調用的函數的執行環境的數組,也能夠說對象,其實關聯數組也就至關於對象。瀏覽器
而後在 ECS 中添加瀏覽器主程序的執行環境 main網絡
建立全局做用域對象 window閉包
main 執行環境引用 window函數
原始類型的全局變量會直接存入 window 環境當中,由於函數是引用類型,因此首先用函數名聲明全局變量工具
而後建立函數對象,封裝函數定義學習
函數對象的 scope 屬性,指回函數建立時的做用域,意思是,函數執行時若是函數自己提供的變量不能讓函數執行徹底,那它便會去回它建立時的那個做用域去尋找變量。ui
函數名後面存入指向函數對象的地址
引用類型在其中只能存儲地址,這個在此筆記談談值傳遞中有詳細說明
向 ECS 中壓入本次函數調用的執行環境元素
建立本次函數調用時使用的函數做用域對象(AO),也就是臨時做用域
在 AO 中建立儲存全部的局部變量,包括形參變量和函數內用 var 聲明的變量
設置 AO 的 parent 屬性和引用函數的 scope 屬性指向父級做用域對象
函數的執行環境引用 AO
順着那個箭頭,先在 AO 中找變量,也就是局部變量,若是 AO 中沒有,再順着箭頭去父級做用域中找
函數的執行環境出棧,AO 釋放,AO 中的局部變量一同被釋放掉。
咱們得知整個結果以後,天然而然那兩個 console
的結果也顯然意見。
前面咱們提到過,全局變量是可重用可是污染全局,局部變量不會污染全局可是不可重用。
我本身認爲閉包就是重用變量又保護變量不被污染的機制,就是爲了解決這一狀況而生的。
包裹受保護的變量和操做變量的內層函數的外層函數
外層函數要返回內層函數的對象
return function(){..}
function
return [function function function]
或 return {fun:function(){...}}
調用外層函數,用外部變量接住返回的內層函數對象,造成閉包。
先貼出示例代碼
function outer() {
var num = 1;
return function() {
console.log(num++);
};
}
var getNum = outer();
getNum();
getNum();
num = 1;
getNum();
複製代碼
下面我把閉包造成的原理用畫圖工具畫出來
window 中存入 outer 名並指向 outer 函數對象,getNum
由於聲明提早也先將變量名存在 window 中。
getNum = outer()
其實包含 outer 的建立和 getNum
的賦值。
上面的圖畫的是 outer 函數進行到 var num = 1;
,前面都有說過,不過多重複。
建立了匿名函數,getNum
指向了匿名函數對象,匿名對象的 scope 指向它的父級做用域,也就是 outer 的做用域,那這樣就造成了圖中的三角關係,此時 outer 執行完畢,離開 ECS 執行環境,outer 的 AO 本也應該隨着離開,可是由於這強大的三角關係,強行拉住不讓其釋放,也就造成了所謂的閉包。
那其實閉包的緣由就是:外層函數的做用域對象沒法釋放
getNum=outer()
getNum 其實就是一個函數
調用getNum()
,會生成 getNum
的臨時做用域,圖中可看出,getNum
其實就是在 outer 中的匿名函數,因此他的 parent 就指向 outer 留下的做用域。當他執行 console.log(num++)
的時候,在他的做用域中沒有 num
變量他就會順着做用域鏈去尋找,最終在 outer 中的做用域中找到 num
並對其進行自加操做。因此當下次調用 getNum 的時候 num 會從 2 開始,不會是一開始的 1。
num 不是全局變量,還實現了 num 變量的重複調用。就達到了閉包的目的。
設置 num = 1
只是在 window
對象上添加存儲 num
的值,當下次調用 getNum
的時候 js 引擎還會從 getNum
做用域開始順着做用域鏈尋找 num
,在 outerAO 就會尋找到 num
,因此根本不會影響到 window 中的 num
,也不會受其影響。所以此段代碼輸出的結果爲 1 2 3
。
固然閉包也有其缺點
比普通函數佔用更多內存,由於外層函數的做用域對象(AO)始終存在
容易形成內存泄漏
將引用內存函數對象的外部變量重置爲 null
getNum = null;
複製代碼
getNum 指向 outer 函數對象的那根線就會斷掉,三角關係破裂,那函數對象和 outerAO 也會相繼被銷燬。
以爲文章不錯的話還請各位大佬給個 star 鼓勵一下 gayhub