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