javascript
JavaScript——执行环境、变量对象、作用域链
前言
這幾天在看《javascript高級程序設計》,看到執行環境和作用域鏈的時候,就有些模糊了。書中還是講的不夠具體。通過上網查資料,特來總結,以備回顧和修正。
目錄:
- EC(執行環境或者執行上下文,Execution Context)
- ECS(執行環境棧Execution Context Stack)
- VO(變量對象,Variable Object)|AO(活動對象,Active Object)
- Scope Chain(作用域鏈)和[[Scope]]屬性
EC——執行環境或執行上下文
每當控制器到達ECMAScript可執行代碼的時候,控制器就進入了一個執行上下文。
JavaScript中,EC分為三種:
- 全局級別的代碼——這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境
- 函數級別的代碼——當執行一個函數式,運行函數體中的代碼
- Eval的代碼——在Eval函數內運行的代碼
EC建立分為倆個階段:
我們可以將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)) }- 進入執行上下文時,VO的初始化過程具體如下:
注意:改過程是有先后順序的。
- 執行代碼階段時,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); // functionvar x = 10; alert(x); // 10 x = 20;function x() {};alert(x); // 20進入執行上下文時:
ECObject={VO:{x:<reference to FunctionDeclaration "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 define?
AO示例:
unction test(a, b) {var c = 10;function d() {}var e = function _e() {};(function x() {}); }test(10); // call當進入test(10)的執行上下文時,它的AO為:
testEC={AO:{arguments:{callee:testlength:1,0:10},a:10,c:undefined,d:<reference to FunctionDeclaration "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:<reference to FunctionDeclaration "d">,e:<reference to FunctionDeclaration "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對象了。
所以,具體我們可以表示如下:
對于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
- f2Context.AO
- f2.[[scope]]
- f1.[[scope]](f1的所有上層EC的VO)
好,我們知道,作用域鏈Scope呢,是用來有序訪問VO|AO中的變量和函數,對于上面的示例,我們給出訪問的過程:
- x,f1
f1的訪問過程類似。
- y
我們發現,在變量和函數的訪問過程,并沒有涉及到[[scope]],那么[[scope]]存在的意義是什么呢?
這個還是看下一篇文章吧。
總結
?
?
轉自:https://segmentfault.com/a/1190000000533094#articleHeader1
轉載于:https://www.cnblogs.com/oneweek/p/8044045.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的JavaScript——执行环境、变量对象、作用域链的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: make: 警告:检测到时钟错误。您的创
- 下一篇: UEFI 文件类型 .efi (二)