摘要:深入系列第三篇,講解執行上下文棧的是如何執行的,也回答了第二篇中的略難的思考題。
順序執行?JavaScript深入系列第三篇,講解執行上下文棧的是如何執行的,也回答了第二篇中的略難的思考題。
如果要問到 JavaScript 代碼執行順序的話,想必寫過 JavaScript 的開發者都會有個直觀的印象,那就是順序執行,畢竟:
var foo = function () { console.log("foo1"); } foo(); // foo1 var foo = function () { console.log("foo2"); } foo(); // foo2
然而去看這段代碼:
function foo() { console.log("foo1"); } foo(); // foo2 function foo() { console.log("foo2"); } foo(); // foo2
打印的結果卻是兩個 foo2。
刷過面試題的都知道這是因為 JavaScript 引擎并非一行一行地分析和執行程序,而是一段一段地分析執行。當執行一段代碼的時候,會進行一個“準備工作”,比如第一個例子中的變量提升,和第二個例子中的函數提升。
但是本文真正想讓大家思考的是:這個“一段一段”中的“段”究竟是怎么劃分的呢?
到底JavaScript引擎遇到一段怎樣的代碼時才會做“準備工作”呢?
可執行代碼這就要說到 JavaScript 的可執行代碼(executable code)的類型有哪些了?
其實很簡單,就三種,全局代碼、函數代碼、eval代碼。
舉個例子,當執行到一個函數的時候,就會進行準備工作,這里的“準備工作”,讓我們用個更專業一點的說法,就叫做"執行上下文(execution contexts)"。
執行上下文棧接下來問題來了,我們寫的函數多了去了,如何管理創建的那么多執行上下文呢?
所以 JavaScript 引擎創建了執行上下文棧(Execution context stack,ECS)來管理執行上下文
為了模擬執行上下文棧的行為,讓我們定義執行上下文棧是一個數組:
ECStack = [];
試想當 JavaScript 開始要解釋執行代碼的時候,最先遇到的就是全局代碼,所以初始化的時候首先就會向執行上下文棧壓入一個全局執行上下文,我們用 globalContext 表示它,并且只有當整個應用程序結束的時候,ECStack 才會被清空,所以 ECStack 最底部永遠有個 globalContext:
ECStack = [ globalContext ];
現在 JavaScript 遇到下面的這段代碼了:
function fun3() { console.log("fun3") } function fun2() { fun3(); } function fun1() { fun2(); } fun1();
當執行一個函數的時候,就會創建一個執行上下文,并且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工作原理,讓我們來看看如何處理上面這段代碼:
// 偽代碼 // fun1() ECStack.push(解答思考題functionContext); // fun1中竟然調用了fun2,還要創建fun2的執行上下文 ECStack.push( functionContext); // 擦,fun2還調用了fun3! ECStack.push( functionContext); // fun3執行完畢 ECStack.pop(); // fun2執行完畢 ECStack.pop(); // fun1執行完畢 ECStack.pop(); // javascript接著執行下面的代碼,但是ECStack底層永遠有個globalContext
好啦,現在我們已經了解了執行上下文棧是如何處理執行上下文的,所以讓我們看看上篇文章《JavaScript深入之詞法作用域和動態作用域》最后的問題:
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope();
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()();
兩段代碼執行的結果一樣,但是兩段代碼究竟有哪些不同呢?
答案就是執行上下文棧的變化不一樣。
讓我們模擬第一段代碼:
ECStack.push(functionContext); ECStack.push( functionContext); ECStack.pop(); ECStack.pop();
讓我們模擬第二段代碼:
ECStack.push(functionContext); ECStack.pop(); ECStack.push( functionContext); ECStack.pop();
是不是有些不同呢?
當然了,這樣概括的回答執行上下文棧的變化不同,是不是依然有一種意猶未盡的感覺呢,為了更詳細講解兩個函數執行上的區別,我們需要探究一下執行上下文到底包含了哪些內容,所以歡迎閱讀下一篇《JavaScript深入之變量對象》。
下一篇文章《JavaScript深入之變量對象》
深入系列JavaScript深入系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列預計寫十五篇左右,旨在幫大家捋順JavaScript底層知識,重點講解如原型、作用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82408.html
摘要:深入系列第七篇,結合之前所講的四篇文章,以權威指南的為例,具體講解當函數執行的時候,執行上下文棧變量對象作用域鏈是如何變化的。前言在深入之執行上下文棧中講到,當代碼執行一段可執行代碼時,會創建對應的執行上下文。 JavaScript深入系列第七篇,結合之前所講的四篇文章,以權威指南的demo為例,具體講解當函數執行的時候,執行上下文棧、變量對象、作用域鏈是如何變化的。 前言 在《Jav...
摘要:本計劃一共期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃,點擊查看前端進階的破冰之旅本期推薦文章深入之執行上下文棧和深入之變量對象,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第一期,本周的主題是調用堆棧,今天是第二天。 本計劃一共28期,每期...
摘要:下面,讓我們以一個函數的創建和激活兩個時期來講解作用域鏈是如何創建和變化的。這時候執行上下文的作用域鏈,我們命名為至此,作用域鏈創建完畢。 JavaScript深入系列第五篇,講述作用鏈的創建過程,最后結合著變量對象,執行上下文棧,讓我們一起捋一捋函數創建和執行的過程中到底發生了什么? 前言 在《JavaScript深入之執行上下文棧》中講到,當JavaScript代碼執行一段可執行代...
摘要:深入系列第八篇,介紹理論上的閉包和實踐上的閉包,以及從作用域鏈的角度解析經典的閉包題。定義對閉包的定義為閉包是指那些能夠訪問自由變量的函數。 JavaScript深入系列第八篇,介紹理論上的閉包和實踐上的閉包,以及從作用域鏈的角度解析經典的閉包題。 定義 MDN 對閉包的定義為: 閉包是指那些能夠訪問自由變量的函數。 那什么是自由變量呢? 自由變量是指在函數中使用的,但既不是函數參數也...
摘要:當遇到函數調用時,引擎為該函數創建一個新的執行上下文并把它壓入當前執行棧的頂部。參考鏈接理解中的執行上下文和執行棧深入之執行上下文棧 開篇 作為一個JavaScript的程序開發者,如果被問到JavaScript代碼的執行順序,你腦海中是不是有一個直觀的印象 -- JavaScript 是順序執行的,可事實真的是這樣的嗎? 讓我們首先看兩個小例子: var foo = functio...
閱讀 1115·2021-11-16 11:42
閱讀 2895·2021-10-12 10:18
閱讀 2854·2021-09-24 09:48
閱讀 3457·2019-08-30 15:56
閱讀 1523·2019-08-30 14:17
閱讀 3036·2019-08-29 12:14
閱讀 902·2019-08-27 10:51
閱讀 2020·2019-08-26 13:28