摘要:也就是說,當代碼執(zhí)行的時候,會進入不同的執(zhí)行上下文,這些執(zhí)行上下文就構成了一個執(zhí)行上下文棧,。它是一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數(shù)聲明。
明白的人,看標題這么寫,會發(fā)現(xiàn)是有問題的,對的,在JavaScript中執(zhí)行上下文與執(zhí)行環(huán)境是同一個東西,標題這么寫肯定是有問題的。但是有些人是搞不清執(zhí)行上下文與執(zhí)行環(huán)境的,所以我才這么寫,以便于他們好搜索到。下面我們統(tǒng)稱執(zhí)行上下文(Execution context,EC)。
在JavaScript的運行過程中,經(jīng)常會遇到一些"奇怪"的行為,不理解為什么JavaScript會這么工作。這時候可能就需要了解一下JavaScript執(zhí)行過程中的相關內容了。
執(zhí)行上下文在JavaScript中有三種代碼運行環(huán)境:
Global Code JavaScript代碼開始運行的默認環(huán)境 Function Code 代碼進入一個JavaScript函數(shù) Eval Code 使用eval()執(zhí)行代碼
為了表示不同的運行環(huán)境,JavaScript中有一個執(zhí)行上下文(Execution context,EC)的概念。也就是說,當JavaScript代碼執(zhí)行的時候,會進入不同的執(zhí)行上下文,這些執(zhí)行上下文就構成了一個執(zhí)行上下文棧(Execution context stack,ECS)。
例如對如下面的JavaScript代碼:
var a = "global var"; function foo(){ console.log(a); } function outerFunc(){ var b = "var in outerFunc"; console.log(b); function innerFunc(){ var c = "var in innerFunc"; console.log(c); foo(); } innerFunc(); } outerFunc()
代碼首先進入Global Execution Context,然后依次進入outerFunc,innerFunc和foo的執(zhí)行上下文,執(zhí)行上下文棧就可以表示為:
當JavaScript代碼執(zhí)行的時候,第一個進入的總是默認的Global Execution Context,所以說它總是在ECS的最底部。
對于每個Execution Context都有三個重要的屬性,變量對象(Variable object,VO),作用域鏈(Scope chain)和this。這三個屬性跟代碼運行的行為有很重要的關系,下面會一一介紹。
當然,除了這三個屬性之外,根據(jù)實現(xiàn)的需要,Execution Context還可以有一些附加屬性。
VO和AO從上面看到,在Execution Context中,會保存變量對象(Variable object,VO),下面就看看變量對象是什么。
變量對象(Variable object)變量對象是與執(zhí)行上下文相關的數(shù)據(jù)作用域。它是一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數(shù)聲明。也就是說,一般VO中會包含以下信息:
變量 (var, Variable Declaration); 函數(shù)聲明 (Function Declaration, FD); 函數(shù)的形參
當JavaScript代碼運行中,如果試圖尋找一個變量的時候,就會首先查找VO。對于前面例子中的代碼,Global Execution Context中的VO就可以表示如下:
注意,假如上面的例子代碼中有下面兩個語句,Global VO仍將不變。
(function bar(){}) // function expression, FE baz = "property of global object"
也就是說,對于VO,是有下面兩種特殊情況的:
1.函數(shù)表達式(與函數(shù)聲明相對)不包含在VO之中活動對象(Activation object)
2.沒有使用var聲明的變量(這種變量是,"全局"的聲明方式,只是給Global添加了一個屬性,并不在VO中)
只有全局上下文的變量對象允許通過VO的屬性名稱間接訪問;在函數(shù)執(zhí)行上下文中,VO是不能直接訪問的,此時由激活對象(Activation Object,縮寫為AO)扮演VO的角色。激活對象 是在進入函數(shù)上下文時刻被創(chuàng)建的,它通過函數(shù)的arguments屬性初始化。
Arguments Objects 是函數(shù)上下文里的激活對象AO中的內部對象,它包括下列屬性:
callee:指向當前函數(shù)的引用 length: 真正傳遞的參數(shù)的個數(shù) properties-indexes:就是函數(shù)的參數(shù)值(按參數(shù)列表從左到右排列)
對于VO和AO的關系可以理解為,VO在不同的Execution Context中會有不同的表現(xiàn):當在Global Execution Context中,可以直接使用VO;但是,在函數(shù)Execution Context中,AO就會被創(chuàng)建。
當上面的例子開始執(zhí)行outerFunc的時候,就會有一個outerFunc的AO被創(chuàng)建:
通過上面的介紹,我們現(xiàn)在了解了VO和AO是什么,以及他們之間的關系了。下面就需要看看JavaScript解釋器是怎么執(zhí)行一段代碼,以及設置VO和AO了。
細看Execution Context當一段JavaScript代碼執(zhí)行的時候,JavaScript解釋器會創(chuàng)建Execution Context,其實這里會有兩個階段:
1.創(chuàng)建階段(當函數(shù)被調用,但是開始執(zhí)行函數(shù)內部代碼之前) 創(chuàng)建Scope chain 創(chuàng)建VO/AO(variables, functions and arguments) 設置this的值 2.激活/代碼執(zhí)行階段 設置變量的值、函數(shù)的引用,然后解釋/執(zhí)行代碼
這里想要詳細介紹一下"創(chuàng)建VO/AO"中的一些細節(jié),因為這些內容將直接影響代碼運行的行為。
對于"創(chuàng)建VO/AO"這一步,JavaScript解釋器主要做了下面的事情:
1.根據(jù)函數(shù)的參數(shù),創(chuàng)建并初始化arguments object 2.掃描函數(shù)內部代碼,查找函數(shù)聲明(Function declaration) 對于所有找到的函數(shù)聲明,將函數(shù)名和函數(shù)引用存入VO/AO中 如果VO/AO中已經(jīng)有同名的函數(shù),那么就進行覆蓋 3.掃描函數(shù)內部代碼,查找變量聲明(Variable declaration) 對于所有找到的變量聲明,將變量名存入VO/AO中,并初始化為"undefined" 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性
看下面的例子:
function foo(i) { var a = "hello"; var b = function privateB() { }; function c() { } } foo(22);
對于上面的代碼,在"創(chuàng)建階段",可以得到下面的Execution Context object:
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { ... } }
在"激活/代碼執(zhí)行階段",Execution Context object就被更新為:
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: "hello", b: pointer to function privateB() }, this: { ... } }例子分析
前面介紹了Execution Context,VO/AO等這么多的理論知識,當然是為了方便我們去分析代碼中的一些行為。這里,就通過幾個簡單的例子,結合上面的概念來分析結果。
Example 1
首先看第一個例子:
(function(){ console.log(bar); console.log(baz); var bar = 20; function baz(){ console.log("baz"); } })()
在Chrome中運行代碼運行后將輸出:
代碼解釋:匿名函數(shù)會首先進入"創(chuàng)建結果",JavaScript解釋器會創(chuàng)建一個"Function Execution Context",然后創(chuàng)建Scope chain,VO/AO和this。根據(jù)前面的介紹,解釋器會掃描函數(shù)和變量聲明,如下的AO會被創(chuàng)建:
所以,對于bar,我們會得到"undefined"這個輸出,表現(xiàn)的行為就是,我們在聲明一個變量之前就訪問了這個變量。這個就是JavaScript中"Hoisting"。
Example 2
接著上面的例子,進行一些修改:
(function(){ console.log(bar); console.log(baz); bar = 20; console.log(window.bar); console.log(bar); function baz(){ console.log("baz"); } })()
運行這段代碼會得到"bar is not defined(…)"錯誤。當代碼執(zhí)行到"console.log(bar);"的時候,會去AO中查找"bar"。但是,根據(jù)前面的解釋,函數(shù)中的"bar"并沒有通過var關鍵字聲明,所有不會被存放在AO中,也就有了這個錯誤。
注釋掉"console.log(bar);",再次運行代碼,可以得到下面結果。"bar"在"激活/代碼執(zhí)行階段"被創(chuàng)建。
Example 3
現(xiàn)在來看最后一個例子:
(function(){ console.log(foo); console.log(bar); console.log(baz); var foo = function(){}; function bar(){ console.log("bar"); } var bar = 20; console.log(bar); function baz(){ console.log("baz"); } })()
代碼的運行結果為:
代碼中,最"奇怪"的地方應該就是"bar"的輸出了,第一次是一個函數(shù),第二次是"20"。
其實也很好解釋,回到前面對"創(chuàng)建VO/AO"的介紹,在創(chuàng)建VO/AO過程中,解釋器會先掃描函數(shù)聲明,然后"foo:
但是,當代碼執(zhí)行到第二句"console.log(bar);"的時候,"激活/代碼執(zhí)行階段"已經(jīng)把AO中的"bar"重新設置了。
作者:田小計劃
出處:http://www.cnblogs.com/wilber...
本文版權歸作者和博客園共有,歡迎轉載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/94854.html
摘要:因此,所有在方法中定義的變量都是放在棧內存中的當我們在程序中創(chuàng)建一個對象時,這個對象將被保存到運行時數(shù)據(jù)區(qū)中,以便反復利用因為對象的創(chuàng)建成本通常較大,這個運行時數(shù)據(jù)區(qū)就是堆內存。 上一篇:《javascript高級程序設計》筆記:繼承近幾篇博客都會圍繞著圖中的知識點展開 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...
摘要:不包括作為其嵌套函數(shù)的被解析的源代碼。作用域鏈當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。棧結構最頂層的執(zhí)行環(huán)境稱為當前運行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。無限制函數(shù)上下文。或者拋出異常退出一個執(zhí)行環(huán)境。 前言 其實規(guī)范這東西不是給人看的,它更多的是給語言實現(xiàn)者提供參考。但是當碰到問題找不到答案時,規(guī)范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發(fā)和思考,如果只讀一...
摘要:執(zhí)行上下文棧通過上文我們知道預處理全局代碼時,會產(chǎn)生一個執(zhí)行上下文環(huán)境。實現(xiàn)這一壓棧出棧過程的機制就是執(zhí)行上下文棧。 JavaScript的解析(預處理)與執(zhí)行 詳見:http://www.cnblogs.com/foodoi... 執(zhí)行上下文 JavaScript在執(zhí)行一個代碼段之前,即解析(預處理)階段,會先進行一些準備工作,例如掃描JS中var定義的變量、函數(shù)名等,進而生成執(zhí)行上...
摘要:在中,通過棧的存取方式來管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調用棧。而處于棧頂?shù)氖钱斍罢趫?zhí)行函數(shù)的執(zhí)行上下文,當函數(shù)調用完成后,它就會從棧頂被推出理想的情況下,閉包會阻止該操作,閉包后續(xù)文章深入詳解。 寫在開篇 已經(jīng)不敢自稱前端小白,曾經(jīng)吹過的牛逼總要一點點去實現(xiàn)。 正如前領導說的,自己喝酒吹過的牛皮,跪著都得含著淚去實現(xiàn)。 那么沒有年終完美總結,來個新年莽撞開始可好。 進擊巨...
摘要:以上簡單總結了下對執(zhí)行上下文和變量對象的理解,主要在于記錄總結一下學習成果,目前文章的水平實在不敢談分享。 執(zhí)行上下文(Execution Context) 文章同步到github javaScript中的執(zhí)行上下文和變量對象 JavaScript代碼執(zhí)行的過程,包括編譯和執(zhí)行兩個階段,編譯就是通過詞法分析,構建抽象抽象語法樹,并編譯成機器識別的指令,在JavaScript代碼編譯階段...
閱讀 1470·2021-09-03 10:29
閱讀 3483·2019-08-29 16:24
閱讀 2079·2019-08-29 11:03
閱讀 1447·2019-08-26 13:52
閱讀 2954·2019-08-26 11:36
閱讀 2816·2019-08-23 17:19
閱讀 582·2019-08-23 17:14
閱讀 838·2019-08-23 13:59