摘要:引擎會(huì)執(zhí)行其執(zhí)行環(huán)境位于堆棧頂部的函數(shù)。當(dāng)函數(shù)執(zhí)行完畢時(shí),當(dāng)前執(zhí)行棧會(huì)從堆棧中彈出去,并且控件將會(huì)到達(dá)其在當(dāng)前堆棧下面的那個(gè)執(zhí)行環(huán)境中。當(dāng)完成以后,它的執(zhí)行環(huán)境會(huì)會(huì)從堆棧中移出,并且控件會(huì)到達(dá)全局執(zhí)行環(huán)境。
如果你想成為一個(gè)Javascript開(kāi)發(fā)者,那么你一定要知道Javascript程序的內(nèi)部運(yùn)行原理。理解執(zhí)行環(huán)境和執(zhí)行棧是非常重要的,其有助于理解其他Javascript的概念,比如說(shuō)提升,作用域和閉包等。
當(dāng)然,理解執(zhí)行環(huán)境和執(zhí)行棧的概念也將會(huì)使你成為一個(gè)更好的Javascript開(kāi)發(fā)者。
閑話少說(shuō),馬上開(kāi)始吧。
執(zhí)行環(huán)境是什么簡(jiǎn)單來(lái)說(shuō),執(zhí)行環(huán)境就是Javascript代碼被計(jì)算和執(zhí)行的環(huán)境的一個(gè)抽象概念。無(wú)論Javascript代碼在什么時(shí)候運(yùn)行,它都會(huì)運(yùn)行在 執(zhí)行環(huán)境中。
執(zhí)行環(huán)境的類型在Javascript中有三種執(zhí)行環(huán)境的類型。
全局執(zhí)行環(huán)境 - 這是一種默認(rèn)和基礎(chǔ)的執(zhí)行環(huán)境。如果代碼不在任何的函數(shù)中,那么它就是在全局執(zhí)行環(huán)境中。他做了兩件事情:首先,它創(chuàng)建了一個(gè)全局對(duì)象 - windows(如果是瀏覽器的話),并且把this的值設(shè)置到全局對(duì)象中。在程序中,只會(huì)存在一個(gè)全局執(zhí)行環(huán)境。
函數(shù)執(zhí)行環(huán)境 - 每次當(dāng)函數(shù)被調(diào)用的時(shí)候,就會(huì)為該函數(shù)創(chuàng)建一個(gè)全新的執(zhí)行環(huán)境。每個(gè)函數(shù)都有他們自己的執(zhí)行環(huán)境,但是他們僅僅是在函數(shù)被調(diào)用的時(shí)候才會(huì)被創(chuàng)建。其可以有任意多個(gè)函數(shù)執(zhí)行環(huán)境。無(wú)論新的執(zhí)行環(huán)境在什么時(shí)候被創(chuàng)建,它都會(huì)按照定義的順序依次執(zhí)行一系列的步驟,不過(guò)這些我們稍后會(huì)講。
eval函數(shù)執(zhí)行環(huán)境 - 在eval函數(shù)中執(zhí)行代碼也會(huì)獲得它自己的執(zhí)行環(huán)境,但是eval并不經(jīng)常被Javascript開(kāi)發(fā)者所使用,所以這里我們目前并不打算討論它。
執(zhí)行棧執(zhí)行棧,在其他編程語(yǔ)言中也被稱為調(diào)用棧,它是一種LIFO(后進(jìn)先出)的結(jié)構(gòu),被用于在代碼執(zhí)行階段存儲(chǔ)所有創(chuàng)建過(guò)的執(zhí)行環(huán)境。
當(dāng)Javascript引擎首次運(yùn)行到你的腳本時(shí),它會(huì)創(chuàng)建一個(gè)全局執(zhí)行環(huán)境,并把它推入到當(dāng)前的執(zhí)行棧中。每當(dāng)引擎運(yùn)行到其函數(shù)調(diào)用時(shí),就會(huì)為這個(gè)函數(shù)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并把它推入到堆棧的頂部。
引擎會(huì)執(zhí)行其執(zhí)行環(huán)境位于堆棧頂部的函數(shù)。當(dāng)函數(shù)執(zhí)行完畢時(shí),當(dāng)前執(zhí)行棧會(huì)從堆棧中彈出去,并且控件將會(huì)到達(dá)其在當(dāng)前堆棧下面的那個(gè)執(zhí)行環(huán)境中。
我們來(lái)通過(guò)下面的代碼示例來(lái)理解:
let a = "Hello World!"; function first() { console.log("Inside first function"); second(); console.log("Again inside first function"); } function second() { console.log("Inside second function"); } first(); console.log("Inside Global Execution Context");
當(dāng)上面的代碼加載到瀏覽器中時(shí),Javascript引擎會(huì)創(chuàng)建一個(gè)全局執(zhí)行環(huán)境,并把它推到當(dāng)前的執(zhí)行棧中。當(dāng)遇到對(duì)first()的調(diào)用時(shí),Javascript引擎會(huì)為這個(gè)函數(shù)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并且把它推到當(dāng)前執(zhí)行棧的頂部。
當(dāng)second()函數(shù)在first()函數(shù)內(nèi)被調(diào)用時(shí),Javascript引擎會(huì)為這個(gè)函數(shù)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并把它推送到當(dāng)前執(zhí)行棧的頂部。當(dāng)second()函數(shù)完成的時(shí)候,它的執(zhí)行環(huán)境會(huì)從當(dāng)前的棧中推出去,并且空間會(huì)到達(dá)當(dāng)前環(huán)境下面的那個(gè)執(zhí)行環(huán)境中,也就是first()函數(shù)執(zhí)行環(huán)境。
當(dāng)first()完成以后,它的執(zhí)行環(huán)境會(huì)會(huì)從堆棧中移出,并且控件會(huì)到達(dá)全局執(zhí)行環(huán)境。當(dāng)所有代碼執(zhí)行完以后,Javascript引擎會(huì)從當(dāng)前棧中移出全局執(zhí)行環(huán)境。
那么執(zhí)行環(huán)境是如何被創(chuàng)建出來(lái)的呢?
到現(xiàn)在為止,我們已經(jīng)看到Javascript引擎是如何管理執(zhí)行環(huán)境的。那么現(xiàn)在咱們來(lái)理解一下執(zhí)行環(huán)境是如何被Javascript引擎創(chuàng)建出來(lái)的吧。
執(zhí)行環(huán)境的創(chuàng)建過(guò)程分為兩個(gè)階段:1,創(chuàng)建階段,2,執(zhí)行階段。
創(chuàng)建階段執(zhí)行環(huán)境是在創(chuàng)建階段被創(chuàng)建出來(lái)的。在創(chuàng)建階段會(huì)發(fā)生下面的事情:
詞法環(huán)境組件被創(chuàng)建出來(lái)。
變量環(huán)境組件被創(chuàng)建出來(lái)。
因此執(zhí)行環(huán)境從概念上可以被表示為:
ExecutionContext = { LexicalEnvironment =詞法環(huán)境, VariableEnvironment = , }
官方ES6文檔定義的詞法環(huán)境如下:
詞法環(huán)境是一種規(guī)范類型,用于根據(jù)ECMAScript代碼的詞法嵌套結(jié)構(gòu)定義標(biāo)識(shí)符與特定變量和函數(shù)的關(guān)聯(lián)。詞法環(huán)境由環(huán)境記錄和一個(gè)對(duì)外部詞匯環(huán)境的可能的空引用組成。
簡(jiǎn)單來(lái)說(shuō),詞法環(huán)境是一個(gè)保存“變量-標(biāo)識(shí)符”映射的結(jié)構(gòu)。(標(biāo)識(shí)符指向變量/函數(shù)的名稱,變量是實(shí)際對(duì)象【包括函數(shù)對(duì)象和數(shù)組對(duì)象】的引用,或者是原始值)
例如,思考下面的代碼片段:
var a = 20; var b = 40; function foo() { console.log("bar"); }
上面的代碼片段的詞法環(huán)境如下:
lexicalEnvironment = { a: 20, b: 40, foo:}
每一個(gè)詞法環(huán)境都有三組件:
環(huán)境記錄
對(duì)外層環(huán)境的引用
this綁定
環(huán)境記錄環(huán)境記錄是變量和函數(shù)聲明的地方,其被存儲(chǔ)在詞法環(huán)境內(nèi)部。
有兩種詞法環(huán)境的類型:
聲明環(huán)境記錄 - 顧名思義,它存儲(chǔ)變量和函數(shù)的聲明。函數(shù)代碼的詞法環(huán)境包含一個(gè)聲明環(huán)境記錄。
對(duì)象環(huán)境記錄 - 全局代碼的詞法環(huán)境包含一個(gè)對(duì)象環(huán)境記錄。除了變量和函數(shù)聲明之外,對(duì)象環(huán)境記錄也會(huì)存儲(chǔ)全局綁定對(duì)象(瀏覽器中的window對(duì)象)。因此對(duì)于每個(gè)綁定對(duì)象的屬性(對(duì)于瀏覽器,它包含所有由瀏覽器給window對(duì)象的屬性和方法),在記錄中創(chuàng)建一個(gè)新的條目。
注意 - 對(duì)于函數(shù)代碼,環(huán)境記錄也會(huì)包含參數(shù)對(duì)象,參數(shù)對(duì)象包含傳遞給函數(shù)的參數(shù)以及索引,和傳遞給函數(shù)的參數(shù)的長(zhǎng)度(個(gè)數(shù))。例如,下面函數(shù)的參數(shù)對(duì)象看起來(lái)像這樣子的:
function foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2},對(duì)外部環(huán)境的引用
對(duì)外部環(huán)境的引用意味著它可以訪問(wèn)外面的詞法環(huán)境。這意味著如果他們?cè)诋?dāng)前的詞法環(huán)境中沒(méi)有找到的話,Javascript引擎會(huì)在外面的環(huán)境里去尋找變量。
this綁定在這個(gè)組件中,this的值是確定的或者是已經(jīng)設(shè)置的。
在全局執(zhí)行環(huán)境中,this的值指向全局對(duì)象。(在瀏覽器中,this指向window對(duì)象)
在函數(shù)執(zhí)行環(huán)境中,this的值依賴于函數(shù)的調(diào)用方式。如果它是在對(duì)象引用中被調(diào)用,this的值就被設(shè)置為那個(gè)對(duì)象,否則,this的值會(huì)被設(shè)置為全局對(duì)象或者是undefined(在嚴(yán)格模式中)。例如:
const person = { name: "peter", birthYear: 1994, calcAge: function() { console.log(2018 - this.birthYear); } } person.calcAge(); // "this" refers to "person", because "calcAge" was called with //"person" object reference const calculateAge = person.calcAge; calculateAge(); // "this" refers to the global window object, because no object reference was given
抽象的說(shuō),在偽代碼中,詞法環(huán)境看起來(lái)像這樣:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here } outer:變量環(huán)境:, this: } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here } outer: , this: } }
它也是一個(gè)詞法環(huán)境,其環(huán)境記錄中環(huán)境記錄保存著在運(yùn)行環(huán)境中的VariableStatements創(chuàng)建的綁定。
正如上面所寫的,變量環(huán)境也是一個(gè)詞法環(huán)境,因此他有如上定義的詞法環(huán)境的所有的屬性和組件。
在ES6中,詞法環(huán)境組件和變量環(huán)境組件的一個(gè)不同點(diǎn)就是前者被用于存儲(chǔ)函數(shù)聲明和變量(let,const)的綁定。而后者只被用于存儲(chǔ)變量(var)的綁定。
執(zhí)行階段在這個(gè)階段,所有的變量賦值都會(huì)完成,所有的代碼最終也都會(huì)執(zhí)行完畢。
例子我們來(lái)看一些例子來(lái)理解上面的概念。
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
當(dāng)上面的代碼被執(zhí)行的時(shí)候,Javascript引擎會(huì)創(chuàng)建一個(gè)全局的執(zhí)行環(huán)境來(lái)執(zhí)行這些全局代碼。因此全局執(zhí)行環(huán)境在創(chuàng)建階段看起來(lái)像這樣子的:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer:, ThisBinding: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: , ThisBinding: } }
在運(yùn)行階段,變量賦值已經(jīng)完成。因此全局執(zhí)行環(huán)境在執(zhí)行階段看起來(lái)就像是這樣的:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: 20, b: 30, multiply: < func > } outer:, ThisBinding: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: , ThisBinding: } }
當(dāng)遇到函數(shù)multiply(20,30)的調(diào)用時(shí),一個(gè)新的函數(shù)執(zhí)行環(huán)境被創(chuàng)建并執(zhí)行函數(shù)中的代碼。因此函數(shù)執(zhí)行環(huán)境在創(chuàng)建階段看起來(lái)像是這樣子的:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer:, ThisBinding: , }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: , ThisBinding: } }
在這以后,執(zhí)行環(huán)境會(huì)經(jīng)歷執(zhí)行階段,這意味著在函數(shù)內(nèi)部賦值給變量的過(guò)程已經(jīng)完成。因此此函數(shù)執(zhí)行環(huán)境在執(zhí)行階段看起來(lái)就像這樣的:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer:, ThisBinding: , }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: 20 }, outer: , ThisBinding: } }
在函數(shù)執(zhí)行完成以后,返回值會(huì)被存儲(chǔ)在c里。因此全局詞法環(huán)境被更新。在這之后,全局代碼執(zhí)行完成,程序運(yùn)行終止。
注意:正如你所注意到的,let和const在創(chuàng)建階段定義的變量沒(méi)有值與他們相關(guān)聯(lián),但是var定義變量會(huì)設(shè)置為false。
這是因?yàn)椋趧?chuàng)建階段,掃描代碼以查找變量和函數(shù)聲明,當(dāng)函數(shù)定義被全部存儲(chǔ)到環(huán)境中時(shí),變量首先會(huì)被初始化為undefined(在var的情況中),或者保持未初始化狀態(tài)(在let和const的情況中)。
這就是你在他們定義之前(雖然是undefined)訪問(wèn)var定義的變量,但是當(dāng)你在定義之前訪問(wèn)let和const定義的變量時(shí),會(huì)得到一個(gè)引用錯(cuò)誤。
這就是我們所謂的提升。
注意 - 在執(zhí)行階段,如果javascript引擎在源代碼中聲明的實(shí)際位置找不到let變量的值,那么它將為其分配未定義的值。
結(jié)論所以我們已經(jīng)討論了如何在內(nèi)部執(zhí)行JavaScript程序。 雖然您沒(méi)有必要將所有這些概念都學(xué)習(xí)成為一名出色的JavaScript開(kāi)發(fā)人員,但對(duì)上述概念有一個(gè)正確的理解將有助于您更輕松,更深入地理解其他概念,如提升,作用域和閉包。
翻譯自:
https://blog.bitsrc.io/unders...
轉(zhuǎn)載自:http://www.lht.ren/article/18/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101959.html
摘要:當(dāng)遇到函數(shù)調(diào)用時(shí),引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并把它壓入當(dāng)前執(zhí)行棧的頂部。參考鏈接理解中的執(zhí)行上下文和執(zhí)行棧深入之執(zhí)行上下文棧 開(kāi)篇 作為一個(gè)JavaScript的程序開(kāi)發(fā)者,如果被問(wèn)到JavaScript代碼的執(zhí)行順序,你腦海中是不是有一個(gè)直觀的印象 -- JavaScript 是順序執(zhí)行的,可事實(shí)真的是這樣的嗎? 讓我們首先看兩個(gè)小例子: var foo = functio...
摘要:為什么會(huì)這樣這段代碼究竟是如何運(yùn)行的執(zhí)行上下文堆棧瀏覽器中的解釋器單線程運(yùn)行。瀏覽器始終執(zhí)行位于堆棧頂部的,并且一旦函數(shù)完成執(zhí)行當(dāng)前操作,它將從堆棧頂部彈出,將控制權(quán)返回到當(dāng)前堆棧中的下方上下文。確定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的執(zhí)行上下文和隊(duì)列(...
摘要:延長(zhǎng)作用域鏈下面兩種語(yǔ)句可以在作用域鏈的前端臨時(shí)增加一個(gè)變量對(duì)象以延長(zhǎng)作用域鏈, 問(wèn)題 今天看筆記發(fā)現(xiàn)自己之前記了一個(gè)關(guān)于同名標(biāo)識(shí)符優(yōu)先級(jí)的內(nèi)容,具體是下面這樣的: 形參優(yōu)先級(jí)高于當(dāng)前函數(shù)名,低于內(nèi)部函數(shù)名 形參優(yōu)先級(jí)高于arguments 形參優(yōu)先級(jí)高于只聲明卻未賦值的局部變量,但是低于聲明且賦值的局部變量 函數(shù)和變量都會(huì)聲明提升,函數(shù)名和變量名同名時(shí),函數(shù)名的優(yōu)先級(jí)要高。執(zhí)行代...
摘要:原文鏈接變量對(duì)象是說(shuō)的執(zhí)行上下文中都有個(gè)對(duì)象用來(lái)存放執(zhí)行上下文中可被訪問(wèn)但是不能被的函數(shù)標(biāo)示符形參變量聲明等。對(duì)于函數(shù)的形參沒(méi)有什么可說(shuō)的,主要看一下函數(shù)的聲明以及變量的聲明兩個(gè)部分。 首先明確幾個(gè)概念: EC:函數(shù)執(zhí)行環(huán)境(或執(zhí)行上下文),Execution Context ECS:執(zhí)行環(huán)境棧,Execution Context Stack VO:變量對(duì)象,Variable Obj...
閱讀 2065·2021-11-22 13:52
閱讀 991·2021-11-17 09:33
閱讀 2719·2021-09-01 10:49
閱讀 2853·2019-08-30 15:53
閱讀 2664·2019-08-29 16:10
閱讀 2438·2019-08-29 11:31
閱讀 1364·2019-08-26 11:40
閱讀 1877·2019-08-26 10:59