站長資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

技術(shù)解答之JavaScript的執(zhí)行機制

本篇文章帶大家?guī)砹薐avaScript中執(zhí)行機制的相關(guān)問題,不論是工作還是面試,我們可能都經(jīng)常會碰到需要知道代碼的執(zhí)行順序的場景,希望對大家有幫助。

技術(shù)解答之JavaScript的執(zhí)行機制

進程與線程

我們都知道計算機的核心是CPU,它承擔(dān)了所有的計算任務(wù);而操作系統(tǒng)是計算機的管理者,它負責(zé)任務(wù)的調(diào)度、資源的分配和管理,統(tǒng)領(lǐng)整個計算機硬件;應(yīng)用程序則是具有某種功能的程序,程序是運行于操作系統(tǒng)之上的。

進程

進程是一個具有獨立功能的程序在一個數(shù)據(jù)集上的一次動態(tài)執(zhí)行的過程,是操作系統(tǒng)進行資源分配和調(diào)度的一個獨立單位,是應(yīng)用程序運行的載體 進程是能擁有資源和獨立運行的最小單位,也是程序執(zhí)行的最小單位。

進程具有的特征:

  • 動態(tài)性:進程是程序的一次執(zhí)行過程,是臨時的,有生命期的,是動態(tài)產(chǎn)生,動態(tài)消亡的;

  • 并發(fā)性:任何進程都可以同其他進程一起并發(fā)執(zhí)行;

  • 獨立性:進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位;

  • 結(jié)構(gòu)性:進程由程序、數(shù)據(jù)和進程控制塊三部分組成。

線程

線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調(diào)度和分派的基本單位。一個進程可以有一個或多個線程,各個線程之間共享程序的內(nèi)存空間(也就是所在進程的內(nèi)存空間)。一個標準的線程由線程ID、當(dāng)前指令指針(PC)、寄存器和堆棧組成。而進程由內(nèi)存空間(代碼、數(shù)據(jù)、進程空間、打開的文件)和一個或多個線程組成。

進程與線程的區(qū)別

  • 線程是程序執(zhí)行的最小單位,而進程是操作系統(tǒng)分配資源的最小單位;

  • 一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執(zhí)行路線;

  • 進程之間相互獨立,但同一進程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進程級的資源(如打開文件和信號),進程與進程之間互不可見;

  • 調(diào)度和切換:線程上下文切換比進程上下文切換要快得多。

JS為什么是單線程?

JavaScript從它誕生之初就是作為瀏覽器的腳本語言,主要用來處理用戶交互以及操作DOM,這就決定了它只能是單線程的,否則會帶來非常復(fù)雜的同步問題。

舉個例子: 如果JS是多線程的,其中一個線程要修改一個DOM元素,另外一個線程想要刪除這個DOM元素,這時候瀏覽器就不知道該聽誰的。所以為了避免復(fù)雜性,從一誕生,JavaScript就被設(shè)計成單線程。

為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質(zhì)

瀏覽器原理

作為前端工程師,瀏覽器想必都不陌生,并且瀏覽器是多進程的。

瀏覽器組成部分

  • 用戶界面:包括地址欄,前進/后退/刷新/書簽

  • 瀏覽器引擎:在用戶界面和呈現(xiàn)引擎之間傳送指令

  • 渲染引擎:用來繪制請求的內(nèi)容

  • 網(wǎng)絡(luò):用來完成網(wǎng)絡(luò)調(diào)用,例如http請求,它具有平臺無關(guān)的接口,可以在不同平臺上工作

  • JavaScript解釋器:用來解析執(zhí)行JavaScript代碼

  • 用戶界面后端:用于繪制基本的窗口小部件,比如組合框和窗口,底層使用操作系統(tǒng)的用戶接口

  • 數(shù)據(jù)存儲:屬于持久層,瀏覽器在硬盤中保存類似cookie的各種數(shù)據(jù),HTML5定義了web database技術(shù),這是一種輕量級完整的客戶端存儲技術(shù)

注意:與大多數(shù)瀏覽器不同的是,谷歌(Chrome)瀏覽器的每個標簽頁都分別對應(yīng)一個呈現(xiàn)引擎實例。每個標簽頁都是一個獨立的進程

瀏覽器包含哪些進程

瀏覽器進程

  • 瀏覽器的主進程(負責(zé)協(xié)調(diào)、主控),該進程只有一個

  • 負責(zé)瀏覽器界面顯示,與用戶交互。如前進,后退等

  • 負責(zé)各個頁面的管理,創(chuàng)建和銷毀其他進程

  • 將渲染(Renderer)進程得到的內(nèi)存中的Bitmap(位圖),繪制到用戶界面上

  • 網(wǎng)絡(luò)資源的管理,下載等

第三方插件進程

負責(zé)管理第三方插件

GPU進程

負責(zé)3D繪制與硬件加速(最多一個)

渲染進程

負責(zé)頁面文檔解析,執(zhí)行與渲染

渲染進程包含哪些線程

GUI渲染線程

主要負責(zé)解析HTML,CSS,構(gòu)建DOM樹,布局,繪制等

該線程與JavaScript引擎線程互斥,當(dāng)執(zhí)行JavaScript引擎線程時,GUI渲染線程會被掛起,當(dāng)任務(wù)隊列空閑時,主線程才會執(zhí)行GUI渲染

JavaScript引擎線程

主要負責(zé)處理JavaScript腳本,執(zhí)行代碼(如V8引擎)

瀏覽器同時只能有一個JS引擎線程在運行JS程序,即JS是單線程的

JS引擎線程與GUI渲染線程是互斥的,所以JS引擎會阻塞頁面渲染

定時觸發(fā)器線程

負責(zé)執(zhí)行定時器函數(shù)(setTimeout,setInterval)

瀏覽器定時計數(shù)器并不是由JS引擎計數(shù)的(因為JS是單線程的,如果處于阻塞狀態(tài)就會影響計數(shù)器的準確性)

通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件觸發(fā)線程的事件隊列中,等待JS引擎空閑后執(zhí)行),這個線程就是定時觸發(fā)器線程,也叫定時器線程

W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms

事件觸發(fā)線程

負責(zé)將準備好的事件交給JS引擎線程執(zhí)行

當(dāng)事件被觸發(fā)時,該線程會把對應(yīng)的事件添加到待處理隊列的隊尾,等待JS引擎處理

異步請求線程

在XMLHttpRequest連接后瀏覽器會開一個線程

檢測請求狀態(tài)變更時,如果有對應(yīng)的回調(diào)函數(shù),異步請求線程就會產(chǎn)生狀態(tài)變更事件,并把對應(yīng)的回調(diào)函數(shù)放入隊列中等待JS引擎執(zhí)行

同步與異步

由于JavaScript是單線程的,這就決定了它的任務(wù)不可能只有同步任務(wù),那些耗時很長的任務(wù)如果也按同步任務(wù)執(zhí)行的話將會導(dǎo)致頁面阻塞,所以JavaScript任務(wù)一般分為兩類:

同步任務(wù)

同步任務(wù)指的是,在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);

異步任務(wù)

異步任務(wù)指的是,不進入主線程、而進入"任務(wù)隊列"(Event queue)的任務(wù),只有"任務(wù)隊列"通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進入主線程執(zhí)行。

常見的異步任務(wù): 定時器,ajax,事件綁定,回調(diào)函數(shù),promise,async await等

  • 同步和異步任務(wù)分別進入不同的執(zhí)行"場所",同步的進入主線程,異步的進入Event Table并注冊函數(shù)。

  • 當(dāng)Event Table中指定的事情完成時,會將這個函數(shù)移入Event Queue。

  • 主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會去Event Queue讀取對應(yīng)的函數(shù),進入主線程執(zhí)行。

  • 上述過程會不斷重復(fù),也就是常說的Event Loop(事件循環(huán))。

  • 我們不禁要問了,那怎么知道主線程執(zhí)行棧為空啊?js引擎存在monitoring process進程,會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。

宏任務(wù)與微任務(wù)

JavaScript除了廣義上的同步任務(wù)與異步任務(wù),還有更精細的任務(wù)定義:

  • 宏任務(wù)(macro-task): 包括全局代碼,setTimeout,setInterval

  • 微任務(wù)(micro-task): new Promise().then(回調(diào)) process.nextTick()

不同類型的任務(wù)會進入到不同的任務(wù)隊列:

技術(shù)解答之JavaScript的執(zhí)行機制

事件循環(huán)的順序,決定js代碼的執(zhí)行順序。進入整體代碼(宏任務(wù))后,開始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)。然后再次從宏任務(wù)開始,找到其中一個任務(wù)隊列執(zhí)行完畢,再執(zhí)行所有的微任務(wù)。

執(zhí)行棧與任務(wù)隊列

執(zhí)行棧

JavaScript代碼都是在執(zhí)行上下文中執(zhí)行的,在JavaScript中有三種執(zhí)行上下文:

  • 全局執(zhí)行上下文

  • 函數(shù)執(zhí)行上下文,JS函數(shù)被調(diào)用時都會創(chuàng)建一個函數(shù)執(zhí)行上下文

  • eval執(zhí)行上下文,eval函數(shù)產(chǎn)生的上下文(用的較少)

通常來說我們的JS代碼都不止一個上下文,那么這些上下文的執(zhí)行順序是怎樣的呢?

我們都知道棧是一種后進先出的數(shù)據(jù)結(jié)構(gòu),我們JavaScript中的執(zhí)行棧就是一種這樣的棧結(jié)構(gòu),當(dāng)JS引擎執(zhí)行代碼時,會產(chǎn)生一個全局上下文并把它壓入執(zhí)行棧,每當(dāng)遇到函數(shù)調(diào)用時,就會產(chǎn)生函數(shù)執(zhí)行上下文并壓入執(zhí)行棧。引擎從棧頂開始執(zhí)行函數(shù),執(zhí)行完后會彈出該執(zhí)行上下文。

function add(){   console.log(1)   foo()   console.log(3) } function foo(){   console.log(2) } add()

我們來看下上面這段代碼的執(zhí)行棧是怎樣的:

技術(shù)解答之JavaScript的執(zhí)行機制

任務(wù)隊列

前面我們說到了JavaScript中所有的任務(wù)分為同步任務(wù)與異步任務(wù),同步任務(wù),顧名思義就是立即執(zhí)行的任務(wù),它一般是直接進入到主線程中執(zhí)行。而我們的異步任務(wù)則是進入任務(wù)隊列等待主線程中的任務(wù)執(zhí)行完再執(zhí)行。

任務(wù)隊列是一個事件的隊列,表示相關(guān)的異步任務(wù)可以進入執(zhí)行棧了。主線程讀取任務(wù)隊列就是讀取里面有哪些事件。

隊列是一種先進先出的數(shù)據(jù)結(jié)構(gòu)。

上面我們說到異步任務(wù)又可以分為宏任務(wù)與微任務(wù),所以任務(wù)隊列也可以分為宏任務(wù)隊列與微任務(wù)隊列

  • Macrotask Queue:進行比較大型的工作,常見的有setTimeout,setInterval,用戶交互操作,UI渲染等;

  • Microtask Queue:進行較小的工作,常見的有Promise,Process.nextTick;

事件循環(huán)(Event-Loop)

同步任務(wù)直接放入到主線程執(zhí)行,異步任務(wù)(點擊事件,定時器,ajax等)掛在后臺執(zhí)行,等待I/O事件完成或行為事件被觸發(fā)。

系統(tǒng)后臺執(zhí)行異步任務(wù),如果某個異步任務(wù)事件(或者行為事件被觸發(fā)),則將該任務(wù)添加到任務(wù)隊列,并且每個任務(wù)會對應(yīng)一個回調(diào)函數(shù)進行處理。

這里異步任務(wù)分為宏任務(wù)與微任務(wù),宏任務(wù)進入到宏任務(wù)隊列,微任務(wù)進入到微任務(wù)隊列。

執(zhí)行任務(wù)隊列中的任務(wù)具體是在執(zhí)行棧中完成的,當(dāng)主線程中的任務(wù)全部執(zhí)行完畢后,去讀取微任務(wù)隊列,如果有微任務(wù)就會全部執(zhí)行,然后再去讀取宏任務(wù)隊列

上述過程會不斷的重復(fù)進行,也就是我們常說的事件循環(huán)(Event-Loop)。

技術(shù)解答之JavaScript的執(zhí)行機制

例題驗證

我們來看道題目進行驗證

(async ()=>{     console.log(1)         setTimeout(() => {     console.log('setTimeout1')     }, 0);        function foo (){         return new Promise((res,rej) => {             console.log(2)             res(3)         })     }        new Promise((resolve,reject)=>{     console.log(4)     resolve()      console.log(5)     }).then(()=> {     console.log('6')     })        const res = await foo();     console.log(res);     console.log('7')        setTimeout(_ => console.log('setTimeout2')) })()

打印順序是:1,4,5,2,6,3,7,setTimeout1,setTimeout2

分析:

代碼自上而下執(zhí)行,先遇到console.log(1),直接打印1,接著遇到定時器屬于宏任務(wù),放入宏任務(wù)隊列

再遇到promise,由于new Promise是一個同步任務(wù),所以直接打印4,遇到resolve,也就是后面的then函數(shù),放入微任務(wù)隊列,再打印5

然后再執(zhí)行await foo,foo函數(shù)里面有個promise,new promise屬于同步任務(wù),所以會直接打印2,await返回的是一個promise的回調(diào),await后面的任務(wù)放入微任務(wù)隊列

最后遇到一個定時器,放入宏任務(wù)隊列

執(zhí)行棧任務(wù)執(zhí)行完了,先去微任務(wù)隊列獲取微任務(wù)執(zhí)行,先執(zhí)行第一個微任務(wù),打印6,再執(zhí)行第二個微任務(wù),打印3,7

微任務(wù)執(zhí)行完,再去宏任務(wù)隊列獲取宏任務(wù)執(zhí)行,打印setTimeout1,setTimeout2

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號
国产成人精品免费大全| 国产日韩精品SUV| 98精品国产高清在线看入口| 99久久精品毛片免费播放| 亚洲日韩在线中文字幕综合| 国产精品美女网站在线看| 精品视频一区二区三区四区| 91国语精品自产拍在线观看一| 久久精品视频16| 国内精品伊人久久久久AV影院| 亚洲精品无码99在线观看 | 五月天婷婷精品视频| 久久棈精品久久久久久噜噜| 久久精品岛国av一区二区无码| 国产乱人伦偷精品视频AAA | 91精品啪在线观看国产线免费 | 国产成人精品123区免费视频| 香蕉久久精品国产| 国产精品卡一卡二卡三| 亚洲国产精品自在自线观看| 97久久久精品综合88久久| 国产精品国产三级专区第1集| 精品国产乱码久久久久软件| 91精品国产一区二区三区左线| 99爱在线精品视频网站| 日韩精品内射视频免费观看| 久久精品午夜福利| 久久久999国产精品| 久久99热精品免费观看牛牛| 久久久久亚洲精品成人网小说| 婷婷精品国产亚洲AV麻豆不片| 久久国产乱子伦精品免费不卡| 久久久久久久久久国产精品免费 | 国产91精品久久久久999| 国产精品久久久久久久久电影网| 北岛玲在线精品视频| 日韩成人无码中文字幕| 亚洲欧美日韩中文二区| 午夜国产精品无套| 国产精品成人小电影在线观看| 久久精品国产72国产精福利|