摘要:在執(zhí)行上下文棧中,全局執(zhí)行上下文處于棧底,頂部為當(dāng)前的執(zhí)行上下文??梢园阉械某绦驁?zhí)行看作一個(gè)執(zhí)行上下文棧,棧的頂部是正在激活的上下文。
前言
??本文內(nèi)容主要涵蓋了執(zhí)行上下文棧、執(zhí)行上下文、變量對(duì)象、函數(shù)變量提升等內(nèi)容。
??眾所周知,JavaScript是單線程編程語(yǔ)言,同一時(shí)間只能做一件事情,程序執(zhí)行順序由上而下,程序的執(zhí)行主要依托JavaScript引擎,JavaScript引擎也并非一行一行的分析執(zhí)行代碼,而是一段一段的分析執(zhí)行。
可執(zhí)行代碼??JavaScript引擎執(zhí)行的代碼當(dāng)然是可執(zhí)行代碼,在JavaScript中可執(zhí)行代碼有三類:全局代碼、函數(shù)代碼以及Eval代碼。
javaScript運(yùn)行原理??JavaScript程序的執(zhí)行主要分語(yǔ)法檢查和運(yùn)行兩個(gè)階段,語(yǔ)法檢查包括詞法分析和語(yǔ)法分析,目的是將JavaScript高級(jí)語(yǔ)言程序轉(zhuǎn)成抽象語(yǔ)法樹。
??語(yǔ)法檢查完成后,到了執(zhí)行階段,執(zhí)行階段包括預(yù)解析和執(zhí)行,預(yù)解析首先會(huì)創(chuàng)建執(zhí)行上下文(本文重點(diǎn)),將語(yǔ)法檢查正確后生成的抽象語(yǔ)法樹復(fù)制到當(dāng)前執(zhí)行上下文中,然后做屬性填充,對(duì)語(yǔ)法樹當(dāng)中的變量名、函數(shù)聲明以及函數(shù)的形參進(jìn)行屬性填充。最后就是執(zhí)行。
??JavaScript運(yùn)行原理會(huì)在后面的文章輸出,不是本文的重點(diǎn),本文只需知道程序運(yùn)行的大致是怎樣的過(guò)程,執(zhí)行上下文何時(shí)創(chuàng)建。
執(zhí)行上下文棧??何為執(zhí)行上下文棧???
??在JavaScript解釋器運(yùn)行階段(預(yù)解析)還維護(hù)了一個(gè)棧,用于管理執(zhí)行上下文。在執(zhí)行上下文棧中,全局執(zhí)行上下文處于棧底,頂部為當(dāng)前的執(zhí)行上下文。當(dāng)頂部的執(zhí)行完成,就會(huì)彈出棧,類似于數(shù)據(jù)結(jié)構(gòu)中的棧,每當(dāng)有當(dāng)前的執(zhí)行上下文執(zhí)行完就會(huì)從棧頂彈出,這種管理執(zhí)行上下文的棧叫做執(zhí)行上下文棧。
??一個(gè)執(zhí)行上下文可以激活另一個(gè)執(zhí)行上下文,類似于函數(shù)調(diào)用另一個(gè)函數(shù),可以一層一層的調(diào)用下去。
??激活其它執(zhí)行上下文的某執(zhí)行上下文被稱為調(diào)用者(caller),被激活的執(zhí)行上下文被稱為被調(diào)用者(callee)。一個(gè)執(zhí)行上下文即可能是調(diào)用者也有可能是被調(diào)用者。
??當(dāng)一個(gè)caller激活了一個(gè)callee時(shí),caller會(huì)暫停自身的執(zhí)行,將控制權(quán)交給callee,此時(shí)該callee被放進(jìn)執(zhí)行上下文棧,稱為進(jìn)行中的上下文,當(dāng)這個(gè)callee上下文結(jié)束后,把控制權(quán)交還給它的caller,caller會(huì)在剛才暫停的地方繼續(xù)執(zhí)行。在這個(gè)caller結(jié)束后,會(huì)繼續(xù)觸發(fā)其他的上下文。
執(zhí)行上下文棧在JavaScript中可以數(shù)組模擬:
ECStack = [];
??當(dāng)瀏覽器首次載入腳本,會(huì)默認(rèn)先進(jìn)入到全局執(zhí)行上下文,位于執(zhí)行上下文棧的最底部,此時(shí)全局代碼會(huì)開(kāi)始初始化,初始化生成相應(yīng)的對(duì)象和函數(shù),在全局上下文執(zhí)行的過(guò)程中可能會(huì)激活一些其他的方法(如果有的話),然后進(jìn)入它們的執(zhí)行上下文,并將元素壓入執(zhí)行上下文棧中??梢园阉械某绦驁?zhí)行看作一個(gè)執(zhí)行上下文棧,棧的頂部是正在激活的上下文。如下表所示:
EC stack | |
---|---|
Active EC | |
... | |
EC | |
Global EC |
??在程序結(jié)束之前,ECStack最底部永遠(yuǎn)是globalContext:
ECStack = [ globalContext ];
?? 看看下面實(shí)例一,是一個(gè)怎么的過(guò)程:
// 實(shí)例一 function bar() { console.log("bar"); } function foo() { bar(); } foo();
??當(dāng)執(zhí)行一個(gè)函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行上下文并壓入執(zhí)行上下文棧中,當(dāng)函數(shù)執(zhí)行完畢,就將該執(zhí)行上下文彈出執(zhí)行上下文棧。
// 創(chuàng)建執(zhí)行上下文棧 ECStack = []; // foo() 創(chuàng)建該函數(shù)執(zhí)行上下文并壓入棧中 ECStack.push(執(zhí)行上下文(Execution Context)functionContext); // foo()中調(diào)用了bar(),創(chuàng)建bar()執(zhí)行上下文并壓入棧中 ECStack.push( functionContext); // bar()執(zhí)行完畢彈出 ECStack.pop(); // foo()執(zhí)行完畢彈出 ECStack.pop();
??執(zhí)行上下文在程序運(yùn)行的預(yù)解析階段創(chuàng)建,預(yù)解析也就是代碼的真正的執(zhí)行前,可以說(shuō)是代碼執(zhí)行前的準(zhǔn)備工作,即創(chuàng)建執(zhí)行上下文。
??執(zhí)行上下文有何用,主要做了三件事:
this綁定;
創(chuàng)建變量對(duì)象;
創(chuàng)建作用域鏈。
??this、作用域和作用域鏈也是JavaScript中很重要的知識(shí)點(diǎn),后面的文章會(huì)詳細(xì)的輸出。
??何為執(zhí)行上下文?
??執(zhí)行上下文理解為是執(zhí)行環(huán)境的抽象概念,當(dāng)JavaScript代碼執(zhí)行一段可執(zhí)行代碼時(shí),都會(huì)創(chuàng)建對(duì)應(yīng)的執(zhí)行上下文,一個(gè)執(zhí)行上下文可以抽象的理解為object,都包括三個(gè)重要屬性:
executionContext: { variable object:vars, functions, arguments scope chain: variable object + all parents scopes thisValue: context object }全局代碼
??全局代碼不包含函數(shù)內(nèi)代碼,在初始化階段,執(zhí)行上下文棧底部有一個(gè)全局執(zhí)行上下文:
ECStack = [ globalContext ];函數(shù)代碼
??當(dāng)進(jìn)入函數(shù)代碼時(shí),函數(shù)執(zhí)行,創(chuàng)建該函數(shù)執(zhí)行上下文并壓入棧中。需要注意的是函數(shù)代碼不包含內(nèi)部函數(shù)代碼。
ECStack = [Eval代碼functionContext ... functionContext globalContext ];
??eval(...)有些陌生,平時(shí)也很少用到,eval(...)函數(shù)可以接受一個(gè)字符串為參數(shù),并將其中的內(nèi)容視為好像在書寫就存在于程序中這個(gè)位置的代碼。
??換句話說(shuō),可以在你寫的代碼中用程序生成代碼并運(yùn)行,就好像是寫在那個(gè)位置的一樣。
eval("var x = 10"); (function foo() { eval("var y = 20"); })(); console.log(x); // 10 console.log(y); // "y is not defined"
?? 上面實(shí)例執(zhí)行過(guò)程:
ECStack = [ globalContext ]; // eval("var x = 10")進(jìn)棧 ECStack.push( evalContext, callingContext: globalContext ); // eval出棧 ECStack.pop(); // foo funciton 進(jìn)棧 ECStack.push(變量對(duì)象functionContext); // eval("var y = 20") 進(jìn)棧 ECStack.push( evalContext, callingContext: functionContext ); // eval出棧 ECStack.pop(); // foo 出棧 ECStack.pop();
??變量對(duì)象(variable object)是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域(scope of data),是與上下文相關(guān)的特殊對(duì)象,用與存儲(chǔ)被定義在上下文中的變量(variables)和函數(shù)聲明(function declarations)。變量對(duì)象是一個(gè)抽象的概念,不同的上下文,它表示使用不同的對(duì)象。
全局變量對(duì)象??全局變量對(duì)象是全局上下文的變量對(duì)象。全局變量對(duì)象就是全局對(duì)象,為啥這么說(shuō):
全局對(duì)象(Global object) 是在進(jìn)入任何執(zhí)行上下文之前就已經(jīng)創(chuàng)建了的對(duì)象;這個(gè)對(duì)象只存在一份,它的屬性在程序中任何地方都可以訪問(wèn),全局對(duì)象的生命周期終止于程序退出那一刻。
全局對(duì)象初始創(chuàng)建階段將Math、String、Date、parseInt作為自身屬性,等屬性初始化,同樣也可以有額外創(chuàng)建的其它對(duì)象作為屬性(其可以指向到全局對(duì)象自身)
在DOM中,全局對(duì)象的window屬性就可以引用全局對(duì)象自身
可以通過(guò)全局上下文的this來(lái)訪問(wèn)全局對(duì)象,同樣也可以遞歸引用自身
當(dāng)訪問(wèn)全局對(duì)象的屬性時(shí)通常會(huì)忽略掉前綴,全局對(duì)象是不能通過(guò)名稱直接訪問(wèn)的
global = { Math: <...>, String: <...>, Date: <...>, ... window: global } console.log(Math.random()); //當(dāng)訪問(wèn)全局對(duì)象的屬性時(shí)通常會(huì)忽略掉前綴;初始創(chuàng)建階段將Math等作為自身屬性 console.log(this.Math.random()); // 通過(guò)this來(lái)訪問(wèn)全局對(duì)象 console.log(this) // window 通過(guò)全局上下文的this來(lái)訪問(wèn)全局對(duì)象 var a = 1; console.log(this.a); // 1 console.log((window.a); // 1 全局對(duì)象有 window 屬性指向自身 console.log(a); // 1 當(dāng)訪問(wèn)全局對(duì)象的屬性時(shí)通常會(huì)忽略掉前綴 this.window.b = 2; console.log(this.b); // 2
??上面的全局對(duì)象的定義和變量對(duì)像的定義對(duì)比,能知道全局變量對(duì)象就是全局對(duì)象,簡(jiǎn)單的說(shuō),因?yàn)樽兞繉?duì)象用于存儲(chǔ)被定義在上下文中的變量和函數(shù)聲明,全局對(duì)象在進(jìn)入任何執(zhí)行上下文前就已經(jīng)創(chuàng)建了,同樣存儲(chǔ)著在全局范圍內(nèi)定義的變量和函數(shù)聲明。
??需要注意的是全局上下文的變量對(duì)象允許通過(guò)VO屬性名稱來(lái)間接訪問(wèn),原因就是全局變量對(duì)象就是全局對(duì)象,在其他上下文中是不能直接VO對(duì)象。
??全局變量對(duì)象VO會(huì)有下列屬性:
函數(shù)聲明(FunctionDeclaration, FD)
所有的變量聲明(var, VariableDeclaration)
不存在所謂的函數(shù)形參
函數(shù)上下文變量對(duì)象(Variable object)??當(dāng)進(jìn)入執(zhí)行上下文時(shí),VO包含來(lái)下列屬性:
函數(shù)形參,屬性名就是參數(shù)名,其值是實(shí)參值,若沒(méi)有傳遞的參數(shù),其值為undefined;
函數(shù)聲明(FunctionDeclaration, FD),由名稱和對(duì)應(yīng)值組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量對(duì)象已經(jīng)存在相同屬性名稱,則完全替換這個(gè)屬性。
所有的變量聲明(var, VariableDeclaration),由名稱和對(duì)應(yīng)值(undefined)組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性。
function foo(a) { var b = 2; var c = function() {}; function bar() { console.log("bar"); } } foo(10);
??當(dāng)進(jìn)入帶有參數(shù)10的foo函數(shù)執(zhí)行上下文時(shí),VO:
VO = { a: 10, bar:, b: undefined, c: undefined }
??在函數(shù)聲明過(guò)程中,如果變量對(duì)象已經(jīng)存在相同的屬性名稱,則完全替換這個(gè)屬性:
function foo(a) { console.log(a); function a() {} } foo(10) // function a(){}
??在變量聲明過(guò)程中,如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性
// 與參數(shù)名同名 function foo(a) { console.log(a); var a = 20; } foo(10) // 10 // 與函數(shù)名同名 function bar(){ console.log(a) var a = 10 function a(){} } bar() // "function a(){}"
??VO創(chuàng)建過(guò)程中,函數(shù)形參的優(yōu)先級(jí)是高于函數(shù)的聲明的,結(jié)果是函數(shù)體內(nèi)部聲明的function a(){}覆蓋了函數(shù)形參a的聲明,因此最后輸出a是一個(gè)function。
??從上面的實(shí)例說(shuō)明,函數(shù)聲明比變量聲明的優(yōu)先級(jí)高,在定義的過(guò)程中不會(huì)被變量覆蓋,除非是賦值:
function foo(a){ var a = 10 function a(){} console.log(a) } foo(20) // 10 function bar(a){ var a function a(){} console.log(a) } bar(20) // "function a(){}"活動(dòng)對(duì)象(Activation object)
??活動(dòng)對(duì)象想必大家對(duì)這個(gè)概念都不陌生,但是容易和變量對(duì)象混淆。
??活動(dòng)對(duì)象就是函數(shù)上下文中的變量對(duì)象,只是不同階段的不同叫法,在創(chuàng)建函數(shù)執(zhí)行上下文階段,變量對(duì)象被創(chuàng)建,變量對(duì)象的屬性不能被訪問(wèn),此時(shí)的函數(shù)還沒(méi)有執(zhí)行,當(dāng)函數(shù)來(lái)到執(zhí)行階段,變量對(duì)象被激活,變成了活動(dòng)對(duì)象,并且里面的屬性都能訪問(wèn)到,開(kāi)始進(jìn)行執(zhí)行階段的操作。
// 執(zhí)行階段 VO -> AO function foo(a) { var b = 2; var c = function() {}; function bar() { console.log("bar"); } } foo(10); VO = { arguments: { 0: 10, length: 1 }, a: 10, bar:, b: undefined, c: undefined } AO = { arguments: { 0: 10, length: 1 }, a: 10, bar: , b: 2, c: reference to FunctionExpression "c" }
??調(diào)用函數(shù)時(shí),會(huì)為其創(chuàng)建一個(gè)Arguments對(duì)象,并自動(dòng)初始化局部變量arguments,指代該Arguments對(duì)象。所有作為參數(shù)傳入的值都會(huì)成為Arguments對(duì)象的數(shù)組元素。
??簡(jiǎn)潔的總結(jié)下上面的內(nèi)容:
全局上下文的變量對(duì)象是全局對(duì)象;
函數(shù)上下文的變量對(duì)象初始化只包含Arguments對(duì)象;
在進(jìn)入執(zhí)行上下文時(shí)會(huì)給變量對(duì)象添加形參、函數(shù)聲明及變量聲明等屬性;
在代碼執(zhí)行,可以通過(guò)賦值修改變量對(duì)象的屬性。
提升??提升一個(gè)很常見(jiàn)的話題,是面試中經(jīng)常被問(wèn)到的一部分,函數(shù)聲明優(yōu)先級(jí)比變量聲明高,這句話應(yīng)該是大部分同學(xué)都會(huì)回答,為啥,上面的內(nèi)容已經(jīng)很好的做出了解釋??聪旅鎸?shí)例:
function test() { console.log(foo); // function foo(){} console.log(bar); // undefined var foo = "Hello"; console.log(foo); // Hello var bar = function () { return "world"; } function foo() { return "hello"; } } test();
// 創(chuàng)建階段 VO = { arguments: { length: 0 }, foo:, // 解釋了第一個(gè)輸出是foo引用,函數(shù)聲明優(yōu)先變量被創(chuàng)建,同名屬性不會(huì)被干擾,在函數(shù)還沒(méi)有被調(diào)用前已經(jīng)被創(chuàng)建了,即能輸出foo的引用 bar: undefined // 解釋了第二個(gè)輸出是undefined,函數(shù)表達(dá)式還是只是一個(gè)變量聲明,不是函數(shù)聲明,不會(huì)被提升 }
// 執(zhí)行階段 VO -> OV OV = { arguments: { length: 0 }, foo: "Hello", // 這里解釋了為什么第三個(gè)輸出值為‘Hello’,做了賦值操作 bar: reference to FunctionExpression "bar" }
// 實(shí)例真實(shí)的執(zhí)行順序 function test() { function foo() { return "hello"; } } var foo; var bar; console.log(foo); console.log(bar); foo = "Hello"; console.log(foo); bar = function () { return "world"; } }
??需要注意的是變量提升只存在使用var關(guān)鍵字聲明變量,如果是使用let聲明變量不存在變量提升。
??聲明變量的作用域限制在其聲明位置的上下文中,在上下文被創(chuàng)建的階段時(shí)創(chuàng)建了,如果沒(méi)有聲明的變量總是全局的,并且是在執(zhí)行階段將賦值給未聲明的變量的值被隱式創(chuàng)建為全局變量,可以通過(guò)delete操作符刪除,聲明變量不可以。
function foo() { console.log(a); // Uncaught ReferenceError: a is not defined;a不存在VO中 a = 1; console.log(a); } foo(); function bar() { a = 1; console.log(a); // 1 可以在全局變量中找到a的值 } bar(); c = 10; console.log(delete c); // true var d = 10; console.log(delete d); // false
??如果清楚上下文相關(guān)的內(nèi)容,提升的問(wèn)題很好的能解答,在學(xué)習(xí)中我們還是需要了解一些底層的知識(shí),這樣有助我們更好的進(jìn)步。
結(jié)語(yǔ)??文章如有不正確的地方歡迎各位大佬指正,也希望有幸看到文章的同學(xué)也有收獲,一起成長(zhǎng)!
——本文首發(fā)于個(gè)人公眾號(hào)———
最后,歡迎大家關(guān)注我的公眾號(hào),一起學(xué)習(xí)交流。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106685.html
摘要:也就是說(shuō),當(dāng)代碼執(zhí)行的時(shí)候,會(huì)進(jìn)入不同的執(zhí)行上下文,這些執(zhí)行上下文就構(gòu)成了一個(gè)執(zhí)行上下文棧,。它是一個(gè)與上下文相關(guān)的特殊對(duì)象,其中存儲(chǔ)了在上下文中定義的變量和函數(shù)聲明。 明白的人,看標(biāo)題這么寫,會(huì)發(fā)現(xiàn)是有問(wèn)題的,對(duì)的,在JavaScript中執(zhí)行上下文與執(zhí)行環(huán)境是同一個(gè)東西,標(biāo)題這么寫肯定是有問(wèn)題的。但是有些人是搞不清執(zhí)行上下文與執(zhí)行環(huán)境的,所以我才這么寫,以便于他們好搜索到。下面我們...
摘要:執(zhí)行上下文的執(zhí)行階段,也有三個(gè)內(nèi)容變量賦值函數(shù)引用執(zhí)行其他代碼。的簡(jiǎn)寫,叫做活動(dòng)對(duì)象。先說(shuō)一下變量對(duì)象,它的結(jié)構(gòu)大致如此,在函數(shù)被調(diào)用的時(shí)候被創(chuàng)建變量對(duì)象包含函數(shù)的形參函數(shù)聲明變量聲明,三個(gè)內(nèi)容。 關(guān)于javascript中的變量對(duì)象和活動(dòng)對(duì)象 我GitHub上的菜鳥倉(cāng)庫(kù)地址: 點(diǎn)擊跳轉(zhuǎn)查看其他相關(guān)文章 文章在我的博客上的地址: 點(diǎn)擊跳轉(zhuǎn) ? ? ? ? 前面的文章說(shuō)到, 執(zhí)行上下...
摘要:關(guān)于提供了一種優(yōu)雅的方式來(lái)隱式傳遞一個(gè)對(duì)象引用,因此可以將設(shè)計(jì)得更加簡(jiǎn)潔并且易于復(fù)用。對(duì)于的誤解新手會(huì)誤認(rèn)為指向函數(shù)本身。這時(shí)候,可以使用的方法強(qiáng)制使指向函數(shù)對(duì)象。的綁定和函數(shù)聲明的位置沒(méi)有任何關(guān)系,只取決于函數(shù)的調(diào)用方式。 關(guān)于this this 提供了一種優(yōu)雅的方式來(lái)隱式傳遞一個(gè)對(duì)象引用,因此可以將API設(shè)計(jì)得更加簡(jiǎn)潔并且易于復(fù)用。 /* *this 隱式傳遞...
摘要:深入系列第四篇,具體講解執(zhí)行上下文中的變量對(duì)象與活動(dòng)對(duì)象。下一篇文章深入之作用域鏈本文相關(guān)鏈接深入之執(zhí)行上下文棧深入系列深入系列目錄地址。 JavaScript深入系列第四篇,具體講解執(zhí)行上下文中的變量對(duì)象與活動(dòng)對(duì)象。全局上下文下的變量對(duì)象是什么?函數(shù)上下文下的活動(dòng)對(duì)象是如何分析和執(zhí)行的?還有兩個(gè)思考題幫你加深印象,快來(lái)看看吧! 前言 在上篇《JavaScript深入之執(zhí)行上下文?!分?..
摘要:一系列活動(dòng)的執(zhí)行上下文從邏輯上形成一個(gè)棧。棧底總是全局上下文,棧頂是當(dāng)前活動(dòng)的執(zhí)行上下文。同樣的,當(dāng)拋出未捕獲的異常時(shí),也會(huì)退出一個(gè)或者多個(gè)執(zhí)行上下文,也會(huì)做相應(yīng)的退棧操作。 概要 本文將向大家介紹ECMAScript的執(zhí)行上下文以及相關(guān)的可執(zhí)行代碼類型。 定義 每當(dāng)控制器到達(dá)ECMAScript可執(zhí)行代碼的時(shí)候,控制器就進(jìn)入了一個(gè)執(zhí)行上下文。 執(zhí)行上下文(簡(jiǎn)稱:EC)是個(gè)抽象的...
閱讀 3402·2021-11-24 09:38
閱讀 1396·2021-11-22 15:08
閱讀 1474·2021-09-29 09:35
閱讀 489·2021-09-02 15:11
閱讀 1314·2019-08-30 12:55
閱讀 398·2019-08-29 17:16
閱讀 501·2019-08-29 11:30
閱讀 428·2019-08-26 13:23