摘要:理解執行上下文和執行堆棧對于理解的其它概念如提升,范圍和閉包至關重要。正確地理解執行上下文和執行堆棧將幫助你更好地使用開發應用。引擎執行位于執行堆棧頂部的方法。當調用時,為該函數創建一個新的執行上下文,并且把它推入到當前執行堆棧。
原文
如果你是或者你想要成為一名js開發者,那么你必須了解js程序內部的運作。理解執行上下文和執行堆棧對于理解js的其它概念(如提升,范圍和閉包)至關重要。
正確地理解執行上下文和執行堆棧將幫助你更好地使用js開發應用。
廢話少說,讓我們開始吧:)
簡單來說,執行上下文是預估和執行當前環境下js代碼的抽象概念。每當在js中運行代碼時,它都在執行上下文中運行。
(譯者:emmm,就是執行上下文包含了追蹤當前正在執行的代碼的全部狀態。)
執行上下文的類型在js中有三種執行類型
全局執行上下文——這是默認或者說基礎執行上下文。函數外的代碼就處于全局執行上下文中。它做了兩件事:它創建了window對象(在瀏覽器環境下),也就是全局對象。并把this指向全局對象。在程序里面只能有一個全局上下文。
函數執行上下文——每次函數被調用,都會為這個函數創建一個新的上下文。每個函數都有自己的上下文,但是只有被調用的時候才會被創建。可以有很多個函數執行上下文。每當創建一個新的函數執行上下文,js引擎都會按照定義好的順序執行一系列的步驟,我將會在下文中討論。
Eval 函數的執行上下文——eval函數執行的時候也會為它里面的代碼創建上下文,但是這個方法用的少,在本文略過。
執行堆棧執行堆棧在其它語言中被稱為“調用棧”,是一種先進后出的一種數據結構,在代碼執行期間被用于存儲所有的執行上下文。
當js引擎開始解析js代碼時,會先創建全局執行上下文并且放在當前執行堆棧中。每當引擎遇到函數調用的代碼時,都會創建該函數的上下文并推入當前執行堆棧中。
引擎執行位于執行堆棧頂部的方法。當方法執行完畢,執行堆棧pop掉最頂部的上下文,接著引擎繼續執行堆棧頂部的方法。
用代碼示范一下:
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");
當上述代碼在瀏覽器內加載,js引擎就會創建一個全局執行上下文并且把它推入當前執行堆棧中。當調用first()時,js為該函數創建一個新的執行上下文,并且把它推入到當前執行堆棧。
當second()方法被first()調用,js引擎為該方法創建一個新的執行上下文并把它推入當前執行堆棧。當second()執行完畢,這個方法的上下文就被執行堆棧推出,并且執行下一個執行上下文,也就是first()。
當first()執行完畢,重復以上步驟。一旦執行了所有代碼,JavaScript引擎就會從當前堆棧中刪除全局執行上下文。
執行上下文如何創建?直到現在,我們已經知道js引擎如何管理執行上下文的了,現在讓我們了解下執行上下文如何被js創建的。
創建執行上下文有兩個階段:1)創建階段,2)執行階段(譯者:???懵逼臉)。
創建階段在執行任何JavaScript代碼之前,執行上下文將經歷創建階段。在創建階段會發生三件事:
this綁定
詞法環境(LexicalEnvironment)創建
變量環境(VariableEnvironment)創建
(譯者:VariableEnvironment和LexicalEnvironment譯者也是第一次聽到,慚愧,大學沒學過編譯原理,在js中還有個this綁定,似乎是js特有)
因此,執行上下文在概念上可以這樣表示,如下:
ExecutionContext = { ThisBinding =this綁定, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
在全局執行上下文中,this值指向全局對象(在瀏覽器內是window對象)。
在函數執行上下文中,this的值取決于函數的調用的時候的情況。如果它由對象引用調用,this值就是該對象,否則this值指向全局或者為undefined(在嚴格模式下)。
例如:
let 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 let calculateAge = person.calcAge; calculateAge(); // "this" refers to the global window object, because no object reference was given詞法環境
官方es6文檔對詞法環境有如下解釋:
詞匯環境是一種規范類型,用于根據ECMAScript代碼的詞法嵌套結構定義標識符與特定變量和函數的關聯。詞匯環境由environment record(譯者:實在不知道咋翻)和外部詞匯環境的可能為null的引用組成。
(譯者:硬翻的,有點怪)
簡而言之,詞匯環境是一種包含標識符變量映射的結構(此處標識符指的是變量/函數的名稱,變量是對實際對象【包括函數類型的對象】或原始值的引用)。
現在,詞法環境由兩部分組成:
(1)environment record
(2)外部環境的引用
1、environment record是存放變量和函數聲明的一個地方
2、對外部環境的引用意味著它可以訪問其外部詞匯環境。
有兩種類型的詞法環境:
一種是全局環境(在全局執行上下文里),它沒有外部環境的引用。它的外部環境引用為null。它有全局對象(window對象)和關聯的方法和屬性(例如數組方法),以及任何用戶定義的全局變量,并且this的值指向全局對象。
一種是函數環境,它存放用戶在函數里定義的變量。并且外部環境可以指向全局環境,或者是外層函數環境。
筆記——對于函數環境,environment record還包含一個arguments對象,該對象包含索引和傳遞給函數的參數之間的映射以及傳遞給函數的參數的長度(數量)。例如,下面函數的參數對象如下所示:
function foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2},
environment record也有兩種類型:
聲明性environment record存儲變量,函數和參數。 一個函數環境包含聲明性environment record。
對象environment record用于定義在全局執行上下文中出現的變量和函數的關聯。全局環境包含對象environment record。
抽象地說,詞法環境在偽代碼中看起來像這樣:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here } outer:變量環境} } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here } outer: } }
它也是一個詞法環境,其EnvironmentRecord包含由此執行上下文中的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);
然后執行上下文會像這樣:
GlobalExectionContext = { ThisBinding:, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: } } FunctionExectionContext = { ThisBinding: , LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: } }
筆記——函數multiply調用時才會創建函數執行上下文。
正如你所注意到的一樣,let和const定義的變量沒有綁定任何值,但var定義的變量為undefined
這是因為在創建階段,掃描代碼尋找變量和函數聲明時,函數聲明完全存儲在環境中,但變量最初設置為undefined(var)或保持為為初始化(let、const)。
(譯者:就是var會聲明提升,而let和const不會)
這就是為什么你可以在變量聲明前訪問到var定義的變量,而訪問let和const定義的變量則會拋出引用錯誤。
這就是js的變量提升。
執行階段這是整篇文章中最簡單的部分。 在此階段,完成對所有這些變量的分配,最后執行代碼。
筆記——在執行階段,如果js引擎在源代碼聲明的實際位置找不到let變量的值,那么它將為其分配undefined值。
結論現在,我們已經了js的部分執行原理,雖然理解了這些概念不一定能讓你成為出色的js開發者,但是明白了上述的概念能讓你更好理解js的其它概念,例如變量提升、閉包。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98959.html
摘要:每次調用函數時,都會創建一個新的執行上下文。理解執行上下文和堆棧可以讓您了解代碼為什么要計算您最初沒有預料到的不同值的原因。 首發:https://www.love85g.com/?p=1723 在這篇文章中,我將深入研究JavaScript最基本的部分之一,即執行上下文。在這篇文章的最后,您應該更清楚地了解解釋器要做什么,為什么在聲明一些函數/變量之前可以使用它們,以及它們的值是如何...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執行這段代碼的時候,調用棧會被清空。之后,產生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執行這段代碼的時候,調用棧會被清空。之后,產生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:調用棧是一種數據結構,它記錄了我們在程序中的位置。當從這個函數返回的時候,就會將這個函數從棧頂彈出,這就是調用棧做的事情。而且這不是唯一的問題,一旦你的瀏覽器開始處理調用棧中的眾多任務,它可能會停止響應相當長一段時間。 原文地址: https://blog.sessionstack.com... PS: 好久沒寫東西了,最近一直在準備寫一個自己的博客,最后一些技術方向已經敲定了,又可以...
摘要:技術上來說這個機制被稱為動態分配或代理。定義類一個類是一個正式的抽象集,它規定了對象的初始狀態和行為。技術上來說一個類表示構造函數原型的組合。因此構造函數創建對象并自動設置新創建實例的原型。第二次調用時,相同的上下文再次被壓入棧并恢復。 原文:JavaScript. The Core: 2nd Edition作者:Dmitry Soshnikov 文章其他語言版本:俄語 這篇文章是 ...
閱讀 2328·2023-04-26 00:28
閱讀 3074·2019-08-30 15:55
閱讀 2747·2019-08-30 12:47
閱讀 1557·2019-08-29 11:04
閱讀 3171·2019-08-28 18:14
閱讀 948·2019-08-28 18:11
閱讀 1676·2019-08-26 18:36
閱讀 3389·2019-08-23 18:21