摘要:如果在全局代碼中調用函數,程序的順序流進入被調用的函數,創建新的執行上下文并將其推送到執行堆棧的頂部。每次調用函數時,都會創建一個新的執行上下文。
翻譯:瘋狂的技術宅
鏈接:http://davidshariff.com/blog/...
本文首發微信公眾號:jingchengyideng
歡迎關注,每天都給你推送新鮮的前端技術文章
在這篇文章中,我將深入探討JavaScript的最基本部分之一,即Execution Context(執行上下文)。 在本文結束時,你應該對解釋器了解得更清楚:為什么在聲明它們之前可以使用某些函數或變量?以及它們的值是如何確定的?
什么是執行上下文?JavaScript的執行環境非常重要,當JavaScript代碼在行時,會被預處理為以下情況之一:
Global code - 首次執行代碼的默認環境。
Function code - 每當執行流程進入函數體時。
Eval code - 要在eval函數內執行的文本。
你可以閱讀大量涉及作用域的在線資料,不過為了使事情更容易理解,讓我們將術語“執行上下文”視為當前代碼的運行環境或作用域。接下來讓我們看一個包含global和function / local上下文的代碼示例。
這里沒有什么特別之處,我們有一個由紫色邊框表示的全局上下文,和由綠色,藍色和橙色邊框表示的3個不同的函數上下文。 只能有1個全局上下文,可以從程序中的任何其他上下文訪問。
你可以擁有任意數量的函數上下文,并且每個函數調用都會創建一個新的上下文,從而創建一個私有作用域,其中無法從當前函數作用域外直接訪問函數內部聲明的任何內容。 在上面的示例中,函數可以訪問在其當前上下文之外聲明的變量,但外部上下文無法訪問在其中聲明的變量或函數。 為什么會這樣呢? 這段代碼究竟是如何處理的?
Execution Context Stack(執行上下文堆棧)瀏覽器中的JavaScript解釋器被實現為單個線程。 實際上這意味著在瀏覽器中一次只能做一件事,其他動作或事件在所謂的執行堆棧中排隊。 下圖是單線程堆棧的抽象視圖:
我們已經知道,當瀏覽器首次加載腳本時,它默認進入全局上下文執行。 如果在全局代碼中調用函數,程序的順序流進入被調用的函數,創建新的執行上下文并將其推送到執行堆棧的頂部。
如果在當前函數中調用另一個函數,則會發生同樣的事情。 代碼的執行流程進入內部函數,該函數創建一個新的執行上下文,該上下文被推送到現有堆棧的頂部。 瀏覽器將始終執行位于堆棧頂部的當前執行上下文,并且一旦函數執行完當前執行上下文后,它將從棧頂部彈出,把控制權返回到當前棧中的下一個上下文。 下面的示例顯示了遞歸函數和程序的執行堆棧:
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
代碼簡單地調用自身3次,并將i的值遞增1。每次調用函數foo時,都會創建一個新的執行上下文。 一旦上下文完成執行,它就會彈出堆棧并且講控制返回到它下面的上下文,直到再次達到全局上下文。
關于執行堆棧execution stack有5個關鍵要點:
單線程。
同步執行。
一個全局上下文。
任意多個函數上下文。
每個函數調用都會創建一個新的執行上下文execution context,甚至是對自身的調用。
執行上下文的細節所以我們現在知道每次調用一個函數時,都會創建一個新的執行上下文。 但是,在JavaScript解釋器中,對執行上下文的每次調用都有兩個階段:
創建階段 [調用函數時,但在執行任何代碼之前]:
創建作用域鏈。
創建變量,函數和參數。
確定“this”的值。
激活/代碼執行階段:
分配值,引用函數和解釋/執行代碼。
可以將每個執行上下文在概念上表示為具有3個屬性的對象:
executionContextObj = { "scopeChain": { /* variableObject + 所有父執行上下文的variableObject */ }, "variableObject": { /* 函數實參/形參,內部變量和函數聲明 */ }, "this": {} }激活對象/變量對象 [AO/VO]
在調用該函數,并且在實際執行函數之前,會創建這個executionContextObj。 這被稱為第1階段,即創造階段。 這時解釋器通過掃描函數傳遞的實參或形參、本地函數聲明和局部變量聲明來創建executionContextObj。 此掃描的結果將成為executionContextObj中的variableObject。
以下是解釋器如何預處理代碼的偽代碼概述:
找一些代碼來調用一個函數。
在執行功能代碼之前,創建執行上下文。
進入創建階段:
初始化作用域鏈。
創建variable object:
創建arguments object,檢查參數的上下文,初始化名稱和值并創建引用副本。
掃描上下文以獲取函數聲明:
對于找到的每個函數,在variable object中創建一個屬性,該屬性是函數的確切名稱,該屬性存在指向內存中函數的引用指針。
如果函數名已存在,則將覆蓋引用指針值。
掃描上下文以獲取變量聲明:
對于找到的每個變量聲明,在variable object中創建一個屬性作為變量名稱,并將該值初始化為undefined。
如果變量名稱已存在于variable object中,則不執行任何操作并繼續掃描。
確定上下文中“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: { ... } }關于hoisting
你可以找到許多使用JavaScript定義術語hoisting的在線資源,解釋變量和函數聲明被hoisting到其函數范圍的頂部。 但是沒有人能夠詳細解釋為什么會發生這種情況,掌握了關于解釋器如何創建激活對象的新知識,很容易理解為什么。 請看下面的代碼示例:
(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顯示為function而不是undefined或string?
即使foo被聲明兩次,我們通過創建階段知道函數在變量之前就被創建在激活對象上了,而且如果激活對象上已經存在了屬性名稱,我們只是繞過了聲明這一步驟。
因此,首先在激活對象上創建對函數foo()的引用,并且當解釋器到達var foo時,我們已經看到屬性名稱foo存在,因此代碼不執行任何操作并繼續處理。
為什么bar未定義?
bar實際上是一個具有函數賦值的變量,我們知道變量是在創建階段被創建的,但它們是使用undefined值初始化的。
總結希望到這里你已經能夠很好地掌握了JavaScript解釋器如何預處理你的代碼。 理解執行上下文和堆??梢宰屇懔私獗澈蟮脑颍簽槭裁创a預處理后的值和你預期的不一樣。
你認為學習解釋器的內部工作原理是多此一舉還是非常必要的呢? 了解執行上下文階段是否能夠幫你你寫出更好的JavaScript呢?
進一步閱讀ECMA-262 5th Edition (http://www.ecma-international...
ECMA-262-3 in detail. Chapter 2. Variable object (http://dmitrysoshnikov.com/ec...
Identifier Resolution, Execution Contexts and scope chains (http://jibbering.com/faq/note...
本文首發微信公眾號:jingchengyideng
歡迎掃描二維碼關注公眾號,每天都給你推送新鮮的前端技術文章
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101368.html
摘要:深入系列第三篇,講解執行上下文棧的是如何執行的,也回答了第二篇中的略難的思考題。 JavaScript深入系列第三篇,講解執行上下文棧的是如何執行的,也回答了第二篇中的略難的思考題。 順序執行? 如果要問到 JavaScript 代碼執行順序的話,想必寫過 JavaScript 的開發者都會有個直觀的印象,那就是順序執行,畢竟: var foo = function () { ...
摘要:也就是說,當代碼執行的時候,會進入不同的執行上下文,這些執行上下文就構成了一個執行上下文棧,。它是一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數聲明。 明白的人,看標題這么寫,會發現是有問題的,對的,在JavaScript中執行上下文與執行環境是同一個東西,標題這么寫肯定是有問題的。但是有些人是搞不清執行上下文與執行環境的,所以我才這么寫,以便于他們好搜索到。下面我們...
摘要:執行上下文和執行棧是中關鍵概念之一,是難點之一。理解執行上下文和執行棧同樣有助于理解其他的概念如提升機制作用域和閉包等。函數執行完成,函數的執行上下文出棧,并且被銷毀。 前言 如果你是一名 JavaScript 開發者,或者想要成為一名 JavaScript 開發者,那么你必須知道 JavaScript 程序內部的執行機制。執行上下文和執行棧是JavaScript中關鍵概念之一,是Ja...
摘要:執行上下文和執行棧是中關鍵概念之一,是難點之一。理解執行上下文和執行棧同樣有助于理解其他的概念如提升機制作用域和閉包等。函數執行完成,函數的執行上下文出棧,并且被銷毀。 前言 如果你是一名 JavaScript 開發者,或者想要成為一名 JavaScript 開發者,那么你必須知道 JavaScript 程序內部的執行機制。執行上下文和執行棧是JavaScript中關鍵概念之一,是Ja...
摘要:當遇到函數調用時,引擎為該函數創建一個新的執行上下文并把它壓入當前執行棧的頂部。參考鏈接理解中的執行上下文和執行棧深入之執行上下文棧 開篇 作為一個JavaScript的程序開發者,如果被問到JavaScript代碼的執行順序,你腦海中是不是有一個直觀的印象 -- JavaScript 是順序執行的,可事實真的是這樣的嗎? 讓我們首先看兩個小例子: var foo = functio...
摘要:首次運行代碼時,會創建一個全局執行上下文并到當前的執行棧中。執行上下文的創建執行上下文分兩個階段創建創建階段執行階段創建階段確定的值,也被稱為。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第一期,本周的主題是調用堆棧,,今天是第一天 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進...
閱讀 1993·2021-11-24 10:45
閱讀 1850·2021-10-09 09:43
閱讀 1291·2021-09-22 15:38
閱讀 1219·2021-08-18 10:19
閱讀 2837·2019-08-30 15:55
閱讀 3057·2019-08-30 12:45
閱讀 2962·2019-08-30 11:25
閱讀 356·2019-08-29 11:30