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