JavaScript閉包的深刻理解

閉包算是javascript中一個比較難理解的概念,想要深刻理解閉包的原理,首先須要搞清楚其餘幾個概念:javascript

1、棧內存和堆內存

學過C/C++的同窗可能知道,計算機系統將內存分爲棧和堆兩部分(大學的基礎課,忘掉的趕忙從新撿起來)。java

棧內存(連續的存儲空間,相似數據結構中的棧):主要用來存放數值、字符、內存地址等小數據數據結構

堆內存(散列的存儲空間,相似數據結構中的鏈表):存放能夠動態變化的大數據閉包

 

2、基本類型和引用類型

JavaScript將變量分爲兩種類型:函數

基本類型:Number、String、Boolean 、undefined、null(值被保存在棧內存中)大數據

引用類型:Object、Array、function(具體內容被保存在堆內存中,在棧內存中僅保存堆內存的地址)spa

如上圖,當在程序中在執行中有以下狀況:code

一、聲明變量a爲基本類型時,直接在棧內存中保存它的值爲100;對象

二、當將a賦值給b時,b在棧內存中新建空間,將a的值複製過來blog

(注:以後a和b就沒有關係了,再改變a或b的值,不影響另一個,它們是獨立的)

三、聲明變量p1爲引用類型時,將p1的內容保存在堆內存中,並將堆內存的物理地址保存在棧內存中

四、當將p1賦值給p2時,p2在棧內存中新建空間,僅複製堆內存的物理地址

(注:p1和p2中都保存的是指向堆內存的地址,即指的是同一個對象,當修改p1對象的屬性後,p2對象的屬性同時被修改)

 

另外,在計算機語言中還有一些很重要的特性:

一、修改基本類型的值,其實是新建空間存一個新值,而後將變量名指向新的空間(舊值依然存在棧內存中,只是缺乏變量名指向它)

二、刪除引用類型,其實並不刪除堆內存中的內容,僅刪除了棧內存中的物理地址(對象的內容依然存在堆內存中,只是缺乏了地址的指向)

(注:計算機關於內存的管理,跟咱們正常想到的不同,例如硬盤恢復就是利用這個原理,爲刪除的內容從新創建一個指向便可訪問)

 

2、變量做用域

javascript中變量又分爲全局變量和局部變量

全局變量:在全局環境中聲明的變量

局部變量:在函數中聲明的變量

當函數在執行時,會建立一個封閉的執行期上下文環境,函數內部聲明的變量僅可在函數內部使用,外部沒法訪問,而全局變量則在任何地方均可以使用

 

3、預編譯

JavaScript的運行爲三步:語法分析》預編譯》解釋執行

一、語法分析:通篇掃描js文件,檢查是否有低級語法錯誤

二、預編譯四部曲:(發生在解釋執行的前一刻)
  a、建立AO對象(執行期上下文對象,全局爲GO)
  b、將形參和變量聲明做爲AO對象的屬性名,值爲undefined
  c、將實參值傳遞給形參,即賦值給AO對象對應屬性名
  d、將函數聲明爲AO對象的方法名,值爲函數體

三、解釋執行:解釋一行,執行一行。

function test(a){ var b=1; function c(){} } test(2); /* 函數預編譯四部曲(函數執行前一刻,不執行不會預編譯),全局預編譯同理 * 1---testAO{} * 2---testAO{a:undefined,b:undefined} * 3---testAO{a:2,b:undefined} * 4---testAO{a:2,b:1,c:function(){}} */

 

4、做用域鏈

每一個JavaScript函數都是一個對象,對象中有些屬性能夠訪問(好比name),有些屬性不能夠訪問(好比[[scope]]僅供js引擎使用)

[[scope]]用來存儲了運行期上下文對象的集合(即做用域鏈),做用域鏈中除了自身建立的AO對象外,還包括了全部父級運行期上下文對象(AO)

function a(){ function b(){ var b = 234; } var a = 123; b(); } var glob = 100; a();

 

當b執行完成後,b的AO要被銷燬,即b的[[scope]]第0位將被置空,若是再次執行b,將新建一個新的AO將其地址存到第0位,

當a也執行完成後,a的AO要被銷燬,即a的[[scope]]第0位將被置空,同時a的AO中存着b,b也將被一同銷燬

 

在瞭解如上這些概念後,咱們再來看下面這個經典的閉包,你會有一個全新的認識

function a(){ var b=123; function c(){ console.log(b+=1); } return c; } var d=a(); d();

當這段代碼在執行時的順序以下:

一、預編譯全局,生成執行上下文對象GO{d:undefined,a:function(){}}

二、定義a函數,將a函數的[[scope]]屬性設置爲{0:GO}

三、預編譯a函數,生成a的執行上下文對象aAO{b:undefined,c:function(){}},修改a函數的[[scope]]屬性爲{0:aAO,1:GO}

四、執行a函數,給aAO的屬性賦值{b:123,c:function(){}}

五、定義c函數,將c函數的[[scope]]屬性設置爲{0:aAO,1:GO},並將c返回給d

六、a函數執行完畢,銷燬[[scope]]屬性第0位對aAO對象的引用

七、執行d函數(等於執行c函數)以前,先預編譯生成c的執行上下文對象cAO{},修改c函數的[[scope]]屬性爲{0:cAO,1:aAO,2:GO}

八、執行c函數,b變量在cAO中沒有,到[[scope]]屬性中的下一位aAO中獲取

 

 

 

個人博客即將搬運同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan

相關文章
相關標籤/搜索