JavaScript 作用域鏈解析

 

最近看了下JavaScript方面的幾本書,把裏面的一些核心概念按照自己的理解做個總結。

 

JavaScript 中有 Scope( 作用域 ) , Scope chain( 作用域鏈 ) , Execute context( 執行上下文 ) , Active Object ( 活動對象 ),Dynamic Scope( 動態作用域 ) , Closure( 閉包 ) 這些概念,要理解這些概念,我們從靜態和動態兩個方面去分析一下。

首先我們寫一個簡單的 function 來做一個例子:

function add(num1, num2){

var sum = num1 + num2;

return sum;

}

我們定義了一個具有兩個形參的 add 函數。

靜態方面:

當創建 add 函數的時候, Javascript 引擎會創建 add 函數的 Scope chain, 這個作用域鏈指向了 Global Context( 全局上下文 ) 。如果用圖形形象化的表述如下圖所示:


 

從上圖可以看出,當 add 函數創建的時候,作用域鏈就已經創建了,因此可以得出一個結論,函數的作用域鏈是創建函數的時候就已經創建了,而不是動態運行期。下面就來看看動態運行期的時候會發生什麼事情。

 

動態方面:

當執行 add 函數的時候, JavaScript 會創建一個 Execute context (執行上下文),執行上下文中就包含了add 函數運行期所需要的所有信息。 Execute context 也有自己的 Scope chain, 當函數運行的時候, JavaScript引擎會首先從用 add 函數的作用域鏈來初始化執行上下文的作用域鏈,然後 JavaScript 引擎又會創建一個 Active Object, 這個對象裏面包含了函數運行期的所有局部變量,參數以及 this 等變量。

如果形象的描述 add 函數動態運行期會發生什麼,可以用如下圖來描述:



 從上圖可以看出,執行上下文是一個動態的概念,它是當函數運行的時候創建的,同時 Active Object 對象也是一個動態的概念,它是被執行上下文的作用域鏈引用的。因此可以得出一個結論:執行上下文和活動對象都是動態概念,並且執行上下文的作用域鏈是由函數作用域鏈初始化的。

上面說了函數作用域和執行上下文作用域,下面接着說一下動態作用域的問題,當在 JavaScript 通過 with 語句, try-catch 的 catch 子句,以及 eval 方法的時候, JavaScript 引擎就會動態的改變執行上下文的作用域。下面還是通過一個例子來看看:

function initUI(){

with (document){ //avoid!

var bd = body,

links = getElementsByTagName("a"),

i= 0,

len = links.length;

while(i < len){

update(links[i++]);

}

getElementById("go-btn").onclick = function(){

start();

};

bd.className = "active";

}

當執行上面的 initUI 函數的時候, JavaScript 會動態的創建一個 with 語句對應的作用域放到執行上下文作用域鏈的最前端,通過下圖可以形象的描述上述過程,下圖紅色標註的區域就顯示了 with 語句產生的作用域。



 最後,我們來看看 JavaScript 最神祕的 Closure (閉包),閉包在 JavaScript 其實就是一個函數,閉包是在函數運行期被創建的,下面還是以一個實例來看看:

function assignEvents(){

var id = "xdi9592";

document.getElementById("save-btn").onclick = function(event){

saveDocument(id);

};

}

當上面的 assignEvents 函數被執行的時候,會創建一個閉包,而這個閉包會引用 assignEvents 作用域中的 id 變量,如果按照傳統的編程語言的方式, id 是存儲在堆棧上的一個變量,當函數執行完了以後 id 就消失,那麼怎麼可能再次引用呢?顯然這裏 JavaScript 採用了另外的方式。下面就來看看 JavaScript 是如何來實現閉包的。當執行 assignEvents 函數的時候, JavaScript 引擎會創建assignEvents函數執行上下文的作用域鏈,這個作用域鏈包含了 assignEvents 執行時的活動對象,而同時 JavaScript 引擎也會創建一個閉包,而閉包的作用域鏈也會引用assignEvent 執行時候的活動對象,這樣當 assignEvents 執行完的時候,雖然它本身執行上下文的作用域鏈不再引用活動對象了,但是閉包還是引用着 assignEvents 運行期對應的活動對象,這就解釋了 JavaScipt 內部的閉包機制。可以用下圖形象的表述上面 assignEvents 函數運行期的情形:



    從上面可以看出,當 assignEvents 函數執行完畢以後, document.getElementById("save-btn").onclick 引用了閉包,這樣當用戶點擊 save-btn 的時候,就會觸發閉包的執行,那麼下面就來看看閉包執行時的情形。前面也說了 JavaScript 中閉包其實就是函數,因此閉包執行和函數執行時的情形是一致的,通過下圖來形象的描述上述onclick 事件所關聯的閉包。

 

 



 從上圖可以看出 JavaScript 引擎首先創建了閉包的執行上下文,然後用閉包作用域鏈來初始化閉包的執行上下文作用域鏈,最後再將閉包執行時對應的活動對象放入到作用域的最前端,這也進一步驗證了閉包就是函數的論斷

發佈了0 篇原創文章 · 獲贊 3 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章