圖解做用域及閉包

引言

網絡上關於做用域及閉包的文章不少,本身對於純理論知識並不能很快的理解,但本身對於圖畫有很強的記憶和理解能力,所以決定將此知識點以圖畫的知識表現出來,加深自身理解的同時若是能幫到正在學習的童鞋就再好不過了git

下面我以函數的整個生命週期來訴說此部分知識github

函數生命週期

先寫一下示例代碼數組

var a = 10;
function func(a) {
  var a = 20;
  a++;
  console.log(a);
}
func();
console.log(a);

開始執行程序前

pic2

  1. 先建立 ECS,ECS 其實就是專門保存正在調用的函數的執行環境的數組,也能夠說對象,其實關聯數組也就至關於對象。
  2. 而後在 ECS 中添加瀏覽器主程序的執行環境 main
  3. 建立全局做用域對象 window
  4. main 執行環境引用 window

定義函數時

pic3

  1. 原始類型的全局變量會直接存入 window 環境當中,由於函數是引用類型,因此首先用函數名聲明全局變量
  2. 而後建立函數對象,封裝函數定義
  3. 函數對象的 scope 屬性,指回函數建立時的做用域,意思是,函數執行時若是函數自己提供的變量不能讓函數執行徹底,那它便會去回它建立時的那個做用域去尋找變量。
  4. 函數名後面存入指向函數對象的地址

引用類型在其中只能存儲地址,這個在此筆記談談值傳遞中有詳細說明瀏覽器

函數調用時

pic4

  1. 向 ECS 中壓入本次函數調用的執行環境元素
  2. 建立本次函數調用時使用的函數做用域對象(AO),也就是臨時做用域
  3. 在 AO 中建立儲存全部的局部變量,包括形參變量和函數內用 var 聲明的變量
  4. 設置 AO 的 parent 屬性和引用函數的 scope 屬性指向父級做用域對象
  5. 函數的執行環境引用 AO
  6. 順着那個箭頭,先在 AO 中找變量,也就是局部變量,若是 AO 中沒有,再順着箭頭去父級做用域中找

函數調用後

pic5

函數的執行環境出棧,AO 釋放,AO 中的局部變量一同被釋放掉。網絡

咱們得知整個結果以後,天然而然那兩個 console 的結果也顯然意見。閉包

閉包

前面咱們提到過,全局變量是可重用可是污染全局,局部變量不會污染全局可是不可重用。函數

我本身認爲閉包就是重用變量又保護變量不被污染的機制,就是爲了解決這一狀況而生的。工具

特色

包裹受保護的變量和操做變量的內層函數的外層函數學習

外層函數要返回內層函數的對象spa

  • 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();

下面我把閉包造成的原理用畫圖工具畫出來

pic6

window 中存入 outer 名並指向 outer 函數對象,getNum 由於聲明提早也先將變量名存在 window 中。

getNum = outer() 其實包含 outer 的建立和 getNum 的賦值。

上面的圖畫的是 outer 函數進行到 var num = 1; ,前面都有說過,不過多重複。

pic7

建立了匿名函數,getNum 指向了匿名函數對象,匿名對象的 scope 指向它的父級做用域,也就是 outer 的做用域,那這樣就造成了圖中的三角關係,此時 outer 執行完畢,離開 ECS 執行環境,outer 的 AO 本也應該隨着離開,可是由於這強大的三角關係,強行拉住不讓其釋放,也就造成了所謂的閉包。

那其實閉包的緣由就是:外層函數的做用域對象沒法釋放

pic8

getNum=outer()getNum 其實就是一個函數

pic9

調用getNum(),會生成 getNum 的臨時做用域,圖中可看出,getNum 其實就是在 outer 中的匿名函數,因此他的 parent 就指向 outer 留下的做用域。當他執行 console.log(num++) 的時候,在他的做用域中沒有 num 變量他就會順着做用域鏈去尋找,最終在 outer 中的做用域中找到 num 並對其進行自加操做。因此當下次調用 getNum 的時候 num 會從 2 開始,不會是一開始的 1

num 不是全局變量,還實現了 num 變量的重複調用。就達到了閉包的目的。

pic10

設置 num = 1 只是在 window 對象上添加存儲 num 的值,當下次調用 getNum 的時候 js 引擎還會從 getNum 做用域開始順着做用域鏈尋找 num,在 outerAO 就會尋找到 num,因此根本不會影響到 window 中的 num,也不會受其影響。所以此段代碼輸出的結果爲 1 2 3

缺點

固然閉包也有其缺點

  • 比普通函數佔用更多內存,由於外層函數的做用域對象(AO)始終存在
  • 容易形成內存泄漏

解決辦法

將引用內存函數對象的外部變量重置爲 null

getNum = null;

pic11

getNum 指向 outer 函數對象的那根線就會斷掉,三角關係破裂,那函數對象和 outerAO 也會相繼被銷燬。

以爲文章不錯的話還請各位大佬給個 star 鼓勵一下 gayhub

相關文章
相關標籤/搜索