圖解做用域及閉包

引言

網絡上關於做用域及閉包的文章不少,本身對於純理論知識並不能很快的理解,但本身對於圖畫有很強的記憶和理解能力,所以決定將此知識點以圖畫的知識表現出來,加深自身理解的同時若是能幫到正在學習的童鞋就再好不過了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 屬性,指回函數建立時的做用域,意思是,函數執行時若是函數自己提供的變量不能讓函數執行徹底,那它便會去回它建立時的那個做用域去尋找變量。ui

  4. 函數名後面存入指向函數對象的地址

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

函數調用時

pic4

  1. 向 ECS 中壓入本次函數調用的執行環境元素

  2. 建立本次函數調用時使用的函數做用域對象(AO),也就是臨時做用域

  3. 在 AO 中建立儲存全部的局部變量,包括形參變量和函數內用 var 聲明的變量

  4. 設置 AO 的 parent 屬性和引用函數的 scope 屬性指向父級做用域對象

  5. 函數的執行環境引用 AO

  6. 順着那個箭頭,先在 AO 中找變量,也就是局部變量,若是 AO 中沒有,再順着箭頭去父級做用域中找

函數調用後

pic5

函數的執行環境出棧,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();
複製代碼

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

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

相關文章
相關標籤/搜索