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