摘要:每次調用函數時,都會創建一個新的執行上下文。理解執行上下文和堆棧可以讓您了解代碼為什么要計算您最初沒有預料到的不同值的原因。
首發:https://www.love85g.com/?p=1723
在這篇文章中,我將深入研究JavaScript最基本的部分之一,即執行上下文。在這篇文章的最后,您應該更清楚地了解解釋器要做什么,為什么在聲明一些函數/變量之前可以使用它們,以及它們的值是如何確定的。
什么是執行上下文?
當代碼在JavaScript中運行時,執行它的環境是非常重要的,并被評估為以下之一:
1:全局代碼——第一次執行代碼的默認環境。
2:函數代碼——每當執行流進入函數體時。
3:要在內部Eval函數中執行的文本。
您可以在線閱讀大量參考資料,其中涉及scope ,本文的目的是使事情更容易理解,讓我們將術語 execution context(執行上下文) 看作當前代碼正在計算的環境/范圍。現在,討論得夠多了,讓我們來看一個包含 global 和 function / local 上下文計算代碼的示例。
這里沒什么特別的,我們有1個 global context 用紫色邊框表示,3個不同的function contexts 用綠色、藍色和橙色邊框表示。只能有一個 global context ,它可以從程序中的任何其他上下文訪問。
您可以有任意數量的 function contexts ,并且每個函數調用都創建一個新的上下文,該上下文創建一個私有范圍,其中函數內部聲明的任何內容都不能從當前函數范圍外部直接訪問。在上面的例子中,一個函數可以訪問當前上下文之外聲明的變量,但是外部上下文不能訪問其中聲明的變量/函數。為什么會這樣?這段代碼究竟是如何計算的?
執行上下文堆棧
瀏覽器中的JavaScript解釋器是作為一個線程實現的。這實際上意味著,在瀏覽器中,一次只能發生一件事,其他操作或事件將排隊在所謂的執行堆棧中。下圖是單線程棧的抽象視圖:
我們已經知道,當瀏覽器第一次加載腳本時,默認情況下它會進入 global execution context 。如果在全局代碼中調用一個函數,程序的序列流將進入被調用的函數,創建一個新的 execution context 并將該上下文推到 execution stack 的頂部。
如果在當前函數中調用另一個函數,也會發生同樣的事情。代碼的執行流進入內部函數,該函數創建一個新的 execution context ,并將其推到現有堆棧的頂部。瀏覽器將始終執行位于堆棧頂部的當前 execution context ,一旦函數執行完當前
execution context ,它將從堆棧頂部彈出,將控制權返回到當前堆棧中下面的上下文。下面的例子展示了一個遞歸函數和程序的 execution stack :
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
代碼簡單地調用自身3次,將i的值增加1。每次調用函數foo時,都會創建一個新的執行上下文。一旦上下文執行完畢,它就會從堆棧中彈出并返回到它下面的上下文,直到再次到達 global context 為止。
關于執行堆棧,有5個關鍵點需要記住:
1:單線程的。
2:同步執行。
3:1個全局上下文。
4:無限的函數上下文。
5:每個函數調用都會創建一個新的執行上下文,甚至是對自身的調用。
詳細執行上下文
現在我們知道,每次調用一個函數,都會創建一個新的 execution context 。然而,在JavaScript解釋器中,對 execution context 的每個調用都有兩個階段:
1:創建階段[當函數被調用,但在執行任何代碼之前]:
創建范圍鏈。
創建變量、函數和參數。
確定“this”的值。
2:激活/代碼執行階段:
為函數賦值、引用并解釋/執行代碼。
可以將每個 execution context (執行上下文)概念上表示為一個具有3個屬性的對象:
executionContextObj = { "scopeChain": { /* 變量對象+所有父執行上下文的變量對象 */ }, "variableObject": { /* 函數參數/參數,內部變量和函數聲明 */ }, "this": {} }
激活/變量對象[AO/VO]
這個 executionContextObj 在調用函數時創建,但在實際函數執行之前創建。這被稱為階段1,創建階段。在這里,解釋器通過掃描函數尋找傳入的參數或參數、局部函數聲明和局部變量聲明來創建executionContextObj 。該掃描的結果成為executionContextObj 中的variableObject。
下面是解釋器如何評估代碼的偽概述:
找到一些代碼來調用函數。
在執行函數代碼之前,創建執行上下文。
進入創作階段:
初始化范圍鏈。
創建變量對象:
創建arguments對象,檢查參數上下文,初始化名稱和值,并創建引用副本。
掃描上下文中的函數聲明:
對于找到的每個函數,在變量對象中創建一個屬性,該屬性是確切的函數名,該函數在內存中有一個指向該函數的引用指針。
如果函數名已經存在,則重寫引用指針值。
掃描上下文變量聲明:
對于找到的每個變量聲明,在變量對象中創建一個屬性,即變量名,并初始化值為undefined。
如果變量名已經存在于變量對象中,則什么也不做,繼續掃描。
確定上下文中“this”的值。
激活/代碼執行階段:
在上下文中運行/解釋函數代碼,并在逐行執行代碼時分配變量值。
讓我們來看一個例子:
function foo(i) { var a = "hello"; var b = function privateB() { }; function c() { } } foo(22);
調用foo(22)時,創建階段如下:
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { ... } }
如您所見,創建階段處理定義屬性的名稱,而不是為它們賦值,只有形式參數/參數例外。創建階段完成后,執行流程進入函數,函數完成執行后,激活/代碼執行階段如下:
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: "hello", b: pointer to function privateB() }, this: { ... } }
關于吊裝的說明
您可以在網上找到許多用JavaScript定義術語提升的資源,解釋變量和函數聲明被提升到函數作用域的頂部。但是,沒有人詳細解釋為什么會發生這種情況,而且有了解釋器如何創建 activation object(激活對象)的新知識,就很容易理解為什么會發生這種情況。以下面的代碼為例:
?(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = "hello", bar = function() { return "world"; }; function foo() { return "hello"; } }());?
我們現在可以回答的問題是:
為什么我們可以在聲明foo之前訪問它?
如果我們遵循創建階段,我們就知道在激活/代碼執行階段之前已經創建了變量。因此,當函數流開始執行時,foo已經在激活對象中定義。
Foo聲明了兩次,為什么Foo是函數而不是未定義或字符串?
盡管foo聲明了兩次,但從創建階段我們就知道函數是在變量之前在激活對象上創建的,如果激活對象上的屬性名已經存在,那么我們只需繞過解密。
因此,首先在激活對象上創建對函數foo()的引用,當解釋器到達var foo時,我們已經看到了屬性名foo的存在,所以代碼什么也不做,繼續執行。
為什么bar沒有定義?
bar實際上是一個具有函數賦值的變量,我們知道這些變量是在創建階段創建的,但是它們是用undefined值初始化的。
總結
希望現在您已經很好地理解了JavaScript解釋器是如何評估代碼的。理解執行上下文和堆棧可以讓您了解代碼為什么要計算您最初沒有預料到的不同值的原因。
您是否認為了解解釋器的內部工作方式對您的JavaScript知識來說是太大的開銷還是必需的?了解執行上下文階段是否有助于編寫更好的JavaScript ?
原文:http://davidshariff.com/blog/...
歡迎關注小程序,感謝您的支持!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/103817.html
摘要:理解執行上下文和執行堆棧對于理解的其它概念如提升,范圍和閉包至關重要。正確地理解執行上下文和執行堆棧將幫助你更好地使用開發應用。引擎執行位于執行堆棧頂部的方法。當調用時,為該函數創建一個新的執行上下文,并且把它推入到當前執行堆棧。 By Sukhjinder Arora | Aug 28, 2018 原文 如果你是或者你想要成為一名js開發者,那么你必須了解js程序內部的運作。理解執行...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執行這段代碼的時候,調用棧會被清空。之后,產生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執行這段代碼的時候,調用棧會被清空。之后,產生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...
摘要:調用棧是一種數據結構,它記錄了我們在程序中的位置。當從這個函數返回的時候,就會將這個函數從棧頂彈出,這就是調用棧做的事情。而且這不是唯一的問題,一旦你的瀏覽器開始處理調用棧中的眾多任務,它可能會停止響應相當長一段時間。 原文地址: https://blog.sessionstack.com... PS: 好久沒寫東西了,最近一直在準備寫一個自己的博客,最后一些技術方向已經敲定了,又可以...
摘要:在這個視頻中,將的調用棧回調隊列和事件循環的內容講的很清晰。調用棧可以往里面放東西,可以在事件結束的時候把回調函數放進回調隊列,然后是事件循環。為的時候這個過程看起來可能不明顯,除非考慮到調用棧的執行環境和事件循環的情況。 譯者按這篇文章可以看做是對Philip Roberts 2014年在JSConf演講的《What the heck is the event loop anyway...
閱讀 532·2024-11-06 13:38
閱讀 832·2024-09-10 13:19
閱讀 971·2024-08-22 19:45
閱讀 1392·2021-11-19 09:40
閱讀 2636·2021-11-18 13:14
閱讀 4300·2021-10-09 10:02
閱讀 2326·2021-08-21 14:12
閱讀 1291·2019-08-30 15:54