摘要:前言這幾天在看高級程序設計,看到執行環境和作用域鏈的時候,就有些模糊了。作用域鏈在執行上下文的作用域中查找變量的過程被稱為標識符解析,這個過程的實現依賴于函數內部另一個同執行上下文相關聯的對象作用域鏈。每個對應一個作用域鏈,,只能有一個,。
前言
這幾天在看《javascript高級程序設計》,看到執行環境和作用域鏈的時候,就有些模糊了。書中還是講的不夠具體。
通過上網查資料,特來總結,以備回顧和修正。
要講的依次為:
EC(執行環境或者執行上下文,Execution Context)
ECS(執行環境棧Execution Context Stack)
VO(變量對象,Variable Object)|AO(活動對象,Active Object)
scope chain(作用域鏈)和[[scope]]屬性
EC每當控制器到達ECMAScript可執行代碼的時候,控制器就進入了一個執行上下文(好高大上的概念啊)。
javascript中,EC分為三種:
全局級別的代碼 – 這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境。
函數級別的代碼 – 當執行一個函數時,運行函數體中的代碼。
Eval的代碼 – 在Eval函數內運行的代碼。
EC建立分為兩個階段:進入執行上下文和執行階段。
1.進入上下文階段:發生在函數調用時,但是在執行具體代碼之前(比如,對函數參數進行具體化之前)
2.執行代碼階段:變量賦值,函數引用,執行其他代碼。
我們可以將EC看做是一個對象。
EC={ VO:{/* 函數中的arguments對象, 參數, 內部的變量以及函數聲明 */}, this:{}, Scope:{ /* VO以及所有父執行上下文中的VO */} }ECS
一系列活動的執行上下文從邏輯上形成一個棧。棧底總是全局上下文,棧頂是當前(活動的)執行上下文。當在不同的執行上下文間切換(退出的而進入新的執行上下文)的時候,棧會被修改(通過壓棧或者退棧的形式)。
壓棧:全局EC-->局部EC1-->局部EC2-->當前EC
出棧:全局EC<--局部EC1<--局部EC2<--當前EC
我們可以用數組的形式來表示環境棧:
ECS=[局部EC,全局EC];
每次控制器進入一個函數(哪怕該函數被遞歸調用或者作為構造器),都會發生壓棧的操作。過程類似javascript數組的push和pop操作。
當javascript代碼文件被瀏覽器載入后,默認最先進入的是一個全局的執行上下文。當在全局上下文中調用執行一個函數時,程序流就進入該被調用函數內,此時引擎就會為該函數創建一個新的執行上下文,并且將其壓入到執行上下文堆棧的頂部。瀏覽器總是執行當前在堆棧頂部的上下文,一旦執行完畢,該上下文就會從堆棧頂部被彈出,然后,進入其下的上下文執行代碼。這樣,堆棧中的上下文就會被依次執行并且彈出堆棧,直到回到全局的上下文。
VO|AO VO每一個EC都對應一個變量對象VO,在該EC中定義的所有變量和函數都存放在其對應的VO中。
VO分為全局上下文VO(全局對象,Global object,我們通常說的global對象)和函數上下文的AO。
VO: { // 上下文中的數據 (變量聲明(var), 函數聲明(FD), 函數形參(function arguments)) }
1.進入執行上下文時,VO的初始化過程具體如下:
函數的形參(當進入函數執行上下文時)
—— 變量對象的一個屬性,其屬性名就是形參的名字,其值就是實參的值;對于沒有傳遞的參數,其值為undefined
函數聲明(FunctionDeclaration, FD) —— 變量對象的一個屬性,其屬性名和值都是函數對象創建出來的;如果變量對象已經包含了相同名字的屬性,則替換它的值
變量聲明(var,VariableDeclaration) —— 變量對象的一個屬性,其屬性名即為變量名,其值為undefined;如果變量名和已經聲明的函數名或者函數的參數名相同,則不會影響已經存在的屬性。
注意:該過程是有先后順序的。
2.執行代碼階段時,VO中的一些屬性undefined值將會確定。
AO在函數的執行上下文中,VO是不能直接訪問的。它主要扮演被稱作活躍對象(activation object)(簡稱:AO)的角色。
這句話怎么理解呢,就是當EC環境為函數時,我們訪問的是AO,而不是VO。
VO(functionContext) === AO;
AO是在進入函數的執行上下文時創建的,并為該對象初始化一個arguments屬性,該屬性的值為Arguments對象。
AO = { arguments: { callee:, length:, properties-indexes: //函數傳參參數值 } };
FD的形式只能是如下這樣:
function f(){ }示例
VO示例:
alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() {}; alert(x); // 20
進入執行上下文時,
ECObject={ VO:{ x:} };
執行代碼時:
ECObject={ VO:{ x:20 //與函數x同名,替換掉,先是10,后變成20 } };
對于以上的過程,我們詳細解釋下。
在進入上下文的時候,VO會被填充函數聲明; 同一階段,還有變量聲明“x”,但是,正如此前提到的,變量聲明是在函數聲明和函數形參之后,并且,變量聲明不會對已經存在的同樣名字的函數聲明和函數形參發生沖突。因此,在進入上下文的階段,VO填充為如下形式:
VO = {}; VO["x"] = <引用了函數聲明"x"> // 發現var x = 10; // 如果函數“x”還未定義 // 則 "x" 為undefined, 但是,在我們的例子中 // 變量聲明并不會影響同名的函數值 VO["x"] = <值不受影響,仍是函數>
執行代碼階段,VO被修改如下:
VO["x"] = 10; VO["x"] = 20;
如下例子再次看到在進入上下文階段,變量存儲在VO中(因此,盡管else的代碼塊永遠都不會執行到,而“b”卻仍然在VO中)
if (true) { var a = 1; } else { var b = 2; } alert(a); // 1 alert(b); // undefined, but not "b is not defined"
AO示例:
function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function x() {}); } test(10); // call
當進入test(10)的執行上下文時,它的AO為:
testEC={ AO:{ arguments:{ callee:test length:1, 0:10 }, a:10, c:undefined, d:, e:undefined } };
由此可見,在建立階段,VO除了arguments,函數的聲明,以及參數被賦予了具體的屬性值,其它的變量屬性默認的都是undefined。函數表達式不會對VO造成影響,因此,(function x() {})并不會存在于VO中。
當執行test(10)時,它的AO為:
testEC={ AO:{ arguments:{ callee:test, length:1, 0:10 }, a:10, c:10, d:, e: } };
可見,只有在這個階段,變量屬性才會被賦具體的值。
作用域鏈在執行上下文的作用域中查找變量的過程被稱為標識符解析(indentifier resolution),這個過程的實現依賴于函數內部另一個同執行上下文相關聯的對象——作用域鏈。作用域鏈是一個有序鏈表,其包含著用以告訴JavaScript解析器一個標識符到底關聯著哪一個變量的對象。而每一個執行上下文都有其自己的作用域鏈Scope。
一句話:作用域鏈Scope其實就是對執行上下文EC中的變量對象VO|AO有序訪問的鏈表。能按順序訪問到VO|AO,就能訪問到其中存放的變量和函數的定義。
Scope定義如下:
Scope = AO|VO + [[Scope]]
其中,AO始終在Scope的最前端,不然為啥叫活躍對象呢。即:
Scope = [AO].concat([[Scope]]);
這說明了,作用域鏈是在函數創建時就已經有了。
那么[[Scope]]是什么呢?
[[Scope]]是一個包含了所有上層變量對象的分層鏈,它屬于當前函數上下文,并在函數創建的時候,保存在函數中。
[[Scope]]是在函數創建的時候保存起來的——靜態的(不變的),只有一次并且一直都存在——直到函數銷毀。 比方說,哪怕函數永遠都不能被調用到,[[Scope]]屬性也已經保存在函數對象上了。
var x=10; function f1(){ var y=20; function f2(){ return x+y; } }
以上示例中,f2的[[scope]]屬性可以表示如下:
f2.[[scope]]=[ f2OuterContext.VO ]
而f2的外部EC的所有上層變量對象包括了f1的活躍對象f1Context.AO,再往外層的EC,就是global對象了。
所以,具體我們可以表示如下:
f2.[[scope]]=[ f1Context.AO, globalContext.VO ]
對于EC執行環境是函數來說,那么它的Scope表示為:
functionContext.Scope=functionContext.AO+function.[[scope]]
注意,以上代碼的表示,也體現了[[scope]]和Scope的差異,Scope是EC的屬性,而[[scope]]則是函數的靜態屬性。
(由于AO|VO在進入執行上下文和執行代碼階段不同,所以,這里及以后Scope的表示,我們都默認為是執行代碼階段的Scope,而對于靜態屬性[[scope]]而言,則是在函數聲明時就創建了)
對于以上的代碼EC,我們可以給出其Scope的表示:
exampelEC={ Scope:[ f2Context.AO+f2.[[scope]], f1.context.AO+f1.[[scope]], globalContext.VO ] }
接下來,我們給出以上其它值的表示:
globalContext.VO
globalContext.VO={ x:10, f1:}
f2Context.AO
f2Context.AO={ f1Context.AO={ arguments:{ callee:f1, length:0 }, y:20, f2:} }
f2.[[scope]]
f2Context.AO={ f1Context.AO:{ arguments:{ callee:f1, length:0 }, y:20, f2:}, globalContext.VO:{ x:10, f1: } }
f1Context.AO
f1Context.AO={ arguments:{ callee:f1, length:0 }, y:20, f2:}
f1.[[scope]](f1的所有上層EC的VO)
f1.[[scope]]={ globalContext.VO:{ x:undefined, f1:undefined } }
好,我們知道,作用域鏈Scope呢,是用來有序訪問VO|AO中的變量和函數,對于上面的示例,我們給出訪問的過程:
x,f1
- "x" -- f2Context.AO // not found -- f1Context.AO // not found -- globalContext.VO // found - 10
f1的訪問過程類似。
y
- "y" -- f2Context.AO // not found -- f1Context.AO // found -20
我們發現,在變量和函數的訪問過程,并沒有涉及到[[scope]],那么[[scope]]存在的意義是什么呢?
這個還是看下一篇文章吧。
總結EC分為兩個階段,進入執行上下文和執行代碼。
ECStack管理EC的壓棧和出棧。
每個EC對應一個作用域鏈Scope,VO|AO(AO,VO只能有一個),this。
函數EC中的Scope在進入函數EC時創建,用來有序訪問該EC對象AO中的變量和函數。
函數EC中的AO在進入函數EC時,確定了Arguments對象的屬性;在執行函數EC時,其它變量屬性具體化。
函數的[[scope]]屬性在函數創建時就已經確定,并保持不變。
參考深入理解javascript之執行上下文(execution context)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78158.html
摘要:全局執行環境的變量對象始終是作用域鏈中的最后一個變量對象。綜上,每個函數對應一個執行環境,每個執行環境對應一個變量對象,而多個變量對象構成了作用域鏈,如果當前執行環境是函數,那么其活動對象在作用域鏈的前端。 1.幾個概念 先說幾個概念:函數、執行環境、變量對象、作用域鏈、活動對象。這幾個東東之間有什么關系呢,往下看~ 函數 函數大家都知道,我想說的是,js中,在函數內部有兩個特殊...
摘要:作用域與作用域鏈每個函數都有自己的執行環境。這是初步了解作用域,如想更深入了解作用域,請看下面鏈接作用域原理作用域鏈由一道題圖解的作用域或者看權威指南和高級程序設計 本文是我學習JavaScript作用域整理的筆記,如有不對,請多指出。 作用域 一個變量的作用域是程序源代碼中定義這個變量的區域。 而在ES5中只分為全局作用域和函數作用域,也就是說for,if,while等語句是不會創建...
摘要:所以,全局執行環境的變量對象始終都是作用域鏈中的最后一個對象。講到這里,可能你已經對執行環境執行環境對象變量對象作用域作用域鏈的理解已經他們之間的關系有了一個較清晰的認識。 JavaScript中的執行環境、作用域、作用域鏈、閉包一直是一個非常有意思的話題,很多博主和大神都分享過相關的文章。這些知識點不僅比較抽象,不易理解,更重要的是與這些知識點相關的問題在面試中高頻出現。之前我也看過...
摘要:講作用域鏈首先要從作用域講起,下面是百度百科里對作用域的定義作用域在許多程序設計語言中非常重要。原文出處談談語法里一些難點問題二 3) 作用域鏈相關的問題 作用域鏈是javascript語言里非常紅的概念,很多學習和使用javascript語言的程序員都知道作用域鏈是理解javascript里很重要的一些概念的關鍵,這些概念包括this指針,閉包等等,它非常紅的另一個重要原因就...
摘要:為了防止之后自己又開始模糊,所以自己來總結一下中關于作用域鏈和原型鏈的知識,并將二者相比較看待進一步加深理解。因此我們發現當多個作用域相互嵌套的時候,就形成了作用域鏈。原型鏈原型說完了作用域鏈,我們來講講原型鏈。 畢業也整整一年了,看著很多學弟都畢業了,忽然心中頗有感慨,時間一去不復還呀。記得從去年這個時候接觸到JavaScript,從一開始就很喜歡這門語言,當時迷迷糊糊看完了《J...
閱讀 3588·2021-09-13 10:28
閱讀 1937·2021-08-10 09:43
閱讀 1010·2019-08-30 15:44
閱讀 3178·2019-08-30 13:14
閱讀 1830·2019-08-29 16:56
閱讀 2938·2019-08-29 16:35
閱讀 2843·2019-08-29 12:58
閱讀 864·2019-08-26 13:46