調(diào)用堆棧閱讀筆記

1. 定義

調(diào)用堆棧是解析器在腳本調(diào)用多個(gè)函數(shù)時(shí),跟蹤當(dāng)前正在執(zhí)行的函數(shù)及下一個(gè)將要被調(diào)用及執(zhí)行的函數(shù)的機(jī)制。

  1. 當(dāng)腳本調(diào)用一個(gè)函數(shù),解釋器將它加入堆棧并開始執(zhí)行函數(shù)
  2. 任何被該函數(shù)調(diào)用的函數(shù)都會(huì)被放入堆棧,并且運(yùn)行到它們被上個(gè)程序調(diào)用的位置
  3. 當(dāng)前函數(shù)執(zhí)行完成后,解釋器將它從堆棧中取出,并在主代碼列表中繼續(xù)執(zhí)行代碼
  4. 如果棧占用的空間比分配的空間還大,會(huì)導(dǎo)致棧溢出錯(cuò)誤。

2. JS引擎

  1. 最流行的引擎是谷歌的V8
  2. 內(nèi)存堆進(jìn)行內(nèi)存分配,調(diào)用棧執(zhí)行代碼
  3. 引擎之外的很多API,都是瀏覽器提供的Web API,例如DOM,AJAX,setTimeout,同時(shí)還有事件循環(huán)和回調(diào)隊(duì)列。
  4. JavaScript 是一門單線程的語言,這意味著它只有一個(gè)調(diào)用棧,因此,它同一時(shí)間只能做一件事。
  5. 一旦你的瀏覽器開始處理調(diào)用棧中的眾多任務(wù),它可能會(huì)停止響應(yīng)相當(dāng)長(zhǎng)一段時(shí)間,如何在不阻塞 UI 的情況下執(zhí)行復(fù)雜的代碼,讓瀏覽器不會(huì)不響應(yīng)?解決方案就是異步回調(diào)。

3. 執(zhí)行上下文

  1. 全局上下文,就是默認(rèn)或者基礎(chǔ)上下文,任何不在函數(shù)內(nèi)部的代碼都在全局上下文中。它會(huì)執(zhí)行兩件事:創(chuàng)建一個(gè)全局的 window 對(duì)象(瀏覽器的情況下),并且設(shè)置 this 的值等于這個(gè)全局對(duì)象。一個(gè)程序中只會(huì)有一個(gè)全局執(zhí)行上下文。
  2. 函數(shù)上下文:函數(shù)在被調(diào)用時(shí),為該函數(shù)單獨(dú)創(chuàng)建自己的執(zhí)行上下文。每當(dāng)一個(gè)新的執(zhí)行上下文被創(chuàng)建,它會(huì)按定義的順序執(zhí)行一系列步驟。
  3. Eval會(huì)被單獨(dú)創(chuàng)建上下文,但是用的比較少,不考慮。
  4. 當(dāng) JavaScript 引擎第一次遇到你的腳本時(shí),它會(huì)創(chuàng)建一個(gè)全局的執(zhí)行上下文并且壓入當(dāng)前執(zhí)行棧。每當(dāng)引擎遇到一個(gè)函數(shù)調(diào)用,它會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并壓入棧的頂部。
    引擎會(huì)執(zhí)行那些執(zhí)行上下文位于棧頂?shù)暮瘮?shù)。當(dāng)該函數(shù)執(zhí)行結(jié)束時(shí),執(zhí)行上下文從棧中彈出,控制流程到達(dá)當(dāng)前棧中的下一個(gè)上下文。
  5. 創(chuàng)建執(zhí)行上下文有創(chuàng)建和執(zhí)行兩個(gè)階段,創(chuàng)建階段,就是this值的綁定,詞法環(huán)境組件和變量環(huán)境組件。詞法環(huán)境就是一種持有標(biāo)識(shí)符-變量映射的結(jié)構(gòu),在詞法環(huán)境的內(nèi)部有兩個(gè)組件:(1) 環(huán)境記錄器和 (2) 一個(gè)外部環(huán)境的引用。
    環(huán)境記錄器是存儲(chǔ)變量和函數(shù)聲明的實(shí)際位置。
    外部環(huán)境的引用意味著它可以訪問其父級(jí)詞法環(huán)境(作用域)。
    變量環(huán)境同樣是一個(gè)詞法環(huán)境,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。
  6. 在 ES6 中,詞法環(huán)境組件和變量環(huán)境的一個(gè)不同就是前者被用來存儲(chǔ)函數(shù)聲明和變量(let 和 const)綁定,而后者只用來存儲(chǔ) var 變量綁定。let和const定義的變量,沒有賦值就是未初始化,而var變量未賦值是undefined。因此var定義的變量可以出現(xiàn)變量提升,而let和const定義的變量不能有。
  7. 在全局執(zhí)行上下文中,this 的值指向全局對(duì)象。(在瀏覽器中,this引用 Window 對(duì)象)。在函數(shù)執(zhí)行上下文中,this 的值取決于該函數(shù)是如何被調(diào)用的。如果它被一個(gè)引用對(duì)象調(diào)用,那么 this 會(huì)被設(shè)置成那個(gè)對(duì)象,否則 this 的值被設(shè)置為全局對(duì)象或者 undefined(在嚴(yán)格模式下)。
  8. 在執(zhí)行階段,如果 JavaScript 引擎不能在源碼中聲明的實(shí)際位置找到 let 變量的值,它會(huì)被賦值為 undefined。

4. JS事件循環(huán)

同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行"場(chǎng)所",同步的進(jìn)入主線程,異步的進(jìn)入Event Table并注冊(cè)函數(shù)。
當(dāng)指定的事情完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移入Event Queue。
主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會(huì)去Event Queue讀取對(duì)應(yīng)的函數(shù),進(jìn)入主線程執(zhí)行。
上述過程會(huì)不斷重復(fù),也就是常說的Event Loop(事件循環(huán))。
我們知道setTimeout這個(gè)函數(shù),是經(jīng)過指定時(shí)間后,把要執(zhí)行的任務(wù)(本例中為task())加入到Event Queue中,又因?yàn)槭菃尉€程任務(wù)要一個(gè)一個(gè)執(zhí)行,如果前面的任務(wù)需要的時(shí)間太久,那么只能等著,導(dǎo)致真正的延遲時(shí)間遠(yuǎn)遠(yuǎn)大于3秒。setTimeout(fn,0)的含義是,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,意思就是不用再等多少秒了,只要主線程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成,棧為空就馬上執(zhí)行。即便主線程為空,0毫秒實(shí)際上也是達(dá)不到的。根據(jù)HTML的標(biāo)準(zhǔn),最低是4毫秒。
對(duì)于setInterval(fn,ms)來說,我們已經(jīng)知道不是每過ms秒會(huì)執(zhí)行一次fn,而是每過ms秒,會(huì)有fn進(jìn)入Event Queue。一旦setInterval的回調(diào)函數(shù)fn執(zhí)行時(shí)間超過了延遲時(shí)間ms,那么就完全看不出來有時(shí)間間隔了。

5. 宏任務(wù)和微任務(wù)

macro-task(宏任務(wù)):包括整體代碼script,setTimeout,setInterval
micro-task(微任務(wù)):Promise,process.nextTick

6. 總結(jié)

  1. JS是一門單線程語言,永遠(yuǎn)都是,不管是什么新框架新語法糖實(shí)現(xiàn)的所謂異步,其實(shí)都是用同步的方法去模擬的。
  2. 事件循環(huán)是js實(shí)現(xiàn)異步的一種方法,也是js的執(zhí)行機(jī)制。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容