LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

[轉(zhuǎn)帖]JavaScript 運(yùn)行機(jī)制 事件循環(huán)(EventLoop)詳解

freeflydom
2023年7月29日 10:10 本文熱度 666

導(dǎo)讀

Javascript 是一種單線程的編程語(yǔ)言,只有一個(gè)調(diào)用棧,決定了它在同一時(shí)間只能做一件事。在代碼執(zhí)行的時(shí)候,通過(guò)將不同函數(shù)的執(zhí)行上下文壓入執(zhí)行棧中來(lái)保證代碼的有序執(zhí)行。在執(zhí)行同步代碼的時(shí)候,如果遇到了異步事件,js 引擎并不會(huì)一直等待其返回結(jié)果,而是會(huì)將這個(gè)事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。因此JS又是一個(gè)非阻塞異步并發(fā)式的編程語(yǔ)言。

進(jìn)程與線程的區(qū)別和聯(lián)系

當(dāng)我們啟動(dòng)某個(gè)程序時(shí),操作系統(tǒng)會(huì)給該程序創(chuàng)建一塊內(nèi)存,用來(lái)存放代碼、運(yùn)行中的數(shù)據(jù)和一個(gè)執(zhí)行任務(wù)的主線程,這樣的運(yùn)行環(huán)境就叫做進(jìn)程

而線程是依附于進(jìn)程的,在進(jìn)程中使用多線程并行處理能提升運(yùn)算效率,進(jìn)程將任務(wù)分成很多細(xì)小的任務(wù),再創(chuàng)建多個(gè)線程,在里面并行分別執(zhí)行

  • 進(jìn)程與進(jìn)程之間完全隔離,互不干擾,由于進(jìn)程之間是相互獨(dú)立的,所以一個(gè)進(jìn)程崩潰不會(huì)影響其他進(jìn)程,如瀏覽器每一個(gè)標(biāo)簽頁(yè)就是一個(gè)獨(dú)立的進(jìn)程,關(guān)閉其中一個(gè)標(biāo)簽頁(yè)別的標(biāo)簽頁(yè)并不會(huì)受到影響。

  • 線程之間的數(shù)據(jù)是共享的,一個(gè)進(jìn)程可以有多個(gè)線程(一個(gè)進(jìn)程至少有一個(gè)線程),當(dāng)一個(gè)進(jìn)程有多個(gè)線程時(shí),每個(gè)線程都有一套獨(dú)立的寄存器和堆棧信息,而代碼、數(shù)據(jù)和文件是共享的

  • 一個(gè)進(jìn)程中的任意一個(gè)線程執(zhí)行出錯(cuò),會(huì)導(dǎo)致這個(gè)進(jìn)程崩潰

  • 當(dāng)一個(gè)進(jìn)程關(guān)閉之后,操作系統(tǒng)會(huì)回收該進(jìn)程的內(nèi)存空間

瀏覽器的進(jìn)程與線程

以大家熟悉的Chrome的內(nèi)核為例,他不僅是多線程的,而且是多進(jìn)程的

最新的Chrome瀏覽器包括:瀏覽器主進(jìn)程GPU進(jìn)程網(wǎng)絡(luò)進(jìn)程渲染進(jìn)程,和插件進(jìn)程

  • 瀏覽器進(jìn)程: 負(fù)責(zé)控制瀏覽器除標(biāo)簽頁(yè)外的界面,包括地址欄、書(shū)簽、前進(jìn)后退按鈕等,以及負(fù)責(zé)與其他進(jìn)程的協(xié)調(diào)工作,同時(shí)提供存儲(chǔ)功能

  • GPU進(jìn)程:負(fù)責(zé)整個(gè)瀏覽器界面的渲染

  • 網(wǎng)絡(luò)進(jìn)程:負(fù)責(zé)發(fā)起和接受網(wǎng)絡(luò)請(qǐng)求

  • 插件進(jìn)程:主要是負(fù)責(zé)插件的運(yùn)行,因?yàn)椴寮赡鼙罎ⅲ孕枰ㄟ^(guò)插件進(jìn)程來(lái)隔離,以保證插件崩潰也不會(huì)對(duì)瀏覽器和頁(yè)面造成影響

  • 渲染進(jìn)程:負(fù)責(zé)控制顯示tab標(biāo)簽頁(yè)內(nèi)的所有內(nèi)容,核心任務(wù)是將HTML、CSS、JS轉(zhuǎn)為用戶可以與之交互的網(wǎng)頁(yè),排版引擎Blink和JS引擎V8都是運(yùn)行在該進(jìn)程中,默認(rèn)情況下Chrome會(huì)為每個(gè)Tab標(biāo)簽頁(yè)創(chuàng)建一個(gè)渲染進(jìn)程

瀏覽器打開(kāi)一個(gè)頁(yè)面至少需要主進(jìn)程、GPU、網(wǎng)絡(luò)和渲染進(jìn)程,后續(xù)如果再打開(kāi)新的標(biāo)簽頁(yè)的話,已經(jīng)創(chuàng)建好的瀏覽器進(jìn)程,GPU進(jìn)程,網(wǎng)絡(luò)進(jìn)程是共享的,不會(huì)重新啟動(dòng),默認(rèn)情況下會(huì)為每一個(gè)標(biāo)簽頁(yè)配置一個(gè)渲染進(jìn)程,但是也有例外,比如同一站點(diǎn)的頁(yè)面間跳轉(zhuǎn)就可能重用渲染進(jìn)程。

我們作為前端最關(guān)心的就是渲染進(jìn)程,那仔細(xì)來(lái)看一下渲染進(jìn)程。

渲染進(jìn)程

上面已經(jīng)提到渲染進(jìn)程負(fù)責(zé)控制顯示tab標(biāo)簽頁(yè)內(nèi)的所有內(nèi)容,核心任務(wù)是將HTML、CSS、JS轉(zhuǎn)為用戶可以與之交互的網(wǎng)頁(yè),排版引擎Blink和JS引擎V8都是運(yùn)行在該進(jìn)程中,默認(rèn)情況下Chrome會(huì)為每個(gè)Tab標(biāo)簽頁(yè)創(chuàng)建一個(gè)渲染進(jìn)程,某個(gè)選項(xiàng)卡崩潰,其他選項(xiàng)卡并不會(huì)受影響。

渲染進(jìn)程中的線程

  • GUI渲染線程:GUI(圖形用戶界面),該線程負(fù)責(zé)渲染頁(yè)面,解析html和CSS、構(gòu)建DOM樹(shù)、CSSOM樹(shù)、渲染樹(shù)(包含要顯示的節(jié)點(diǎn)和節(jié)點(diǎn)的樣式信息,即整合 DOM 和 CSSOM 信息)、布局計(jì)算(計(jì)算節(jié)點(diǎn)在頁(yè)面的位置和大小)、和繪制頁(yè)面(遍歷渲染樹(shù),調(diào)用 GPU 繪制,顯示在頁(yè)面上),重繪重排(回流)也是在該線程執(zhí)行,GUI更新會(huì)被保存在一個(gè)隊(duì)列中,等到JS引擎空閑時(shí),立即被執(zhí)行。

  • JS引擎線程:一個(gè)tab頁(yè)中只有一個(gè)JS引擎線程(單線程),負(fù)責(zé)解析和執(zhí)行JS。這個(gè)線程就是負(fù)責(zé)執(zhí)行JS的主線程,"JS是單線程的"就是指的這個(gè)線程。大名鼎鼎的Chrome V8引擎就是在這個(gè)線程運(yùn)行的。需要注意的是,這個(gè)線程跟GUI線程是互斥的。互斥的原因是JS也可以操作DOM,如果JS線程和GUI線程同時(shí)操作DOM,結(jié)果就混亂了,不知道到底渲染哪個(gè)結(jié)果。這帶來(lái)的后果就是如果JS長(zhǎng)時(shí)間運(yùn)行,GUI線程就不能執(zhí)行,整個(gè)頁(yè)面就感覺(jué)卡死了。

  • 計(jì)時(shí)器線程:指setInterval和setTimeout,因?yàn)镴S引擎是單線程的,所以如果處于阻塞狀態(tài),那么計(jì)時(shí)器就會(huì)不準(zhǔn)了,所以需要單獨(dú)的線程來(lái)負(fù)責(zé)計(jì)時(shí)器工作。

  • 異步http請(qǐng)求線程:這個(gè)線程負(fù)責(zé)處理異步的ajax請(qǐng)求,當(dāng)請(qǐng)求完成后,他也會(huì)通知事件觸發(fā)線程,然后事件觸發(fā)線程將這個(gè)事件放入事件隊(duì)列給主線程執(zhí)行。

  • 事件觸發(fā)線程:定時(shí)器線程其實(shí)只是一個(gè)計(jì)時(shí)的作用,他并不會(huì)真正執(zhí)行時(shí)間到了的回調(diào),真正執(zhí)行這個(gè)回調(diào)的還是JS主線程。所以當(dāng)時(shí)間到了定時(shí)器線程會(huì)將這個(gè)回調(diào)事件給到事件觸發(fā)線程,然后事件觸發(fā)線程將它加到任務(wù)隊(duì)列里面去。最終JS主線程從任務(wù)隊(duì)列取出這個(gè)回調(diào)執(zhí)行。事件觸發(fā)線程管理著一個(gè)任務(wù)隊(duì)列,事件觸發(fā)線程不僅會(huì)將定時(shí)器事件放入任務(wù)隊(duì)列,其他滿足條件的事件也是他負(fù)責(zé)放進(jìn)任務(wù)隊(duì)列,如鼠標(biāo)點(diǎn)擊事件等。

setTimeout、DOM或者 HTTP請(qǐng)求這部分其實(shí)并不在 v8 引擎中,這些屬于webAPIs,即瀏覽器的API,不是js引擎提供的。

所謂的事件循環(huán),或者說(shuō)js能夠?qū)崿F(xiàn)異步非阻塞特性的基礎(chǔ)就是因?yàn)槎嗑€程設(shè)計(jì)的存在。

消化總結(jié):

用戶啟動(dòng)某個(gè)應(yīng)用程序會(huì)建立一個(gè)或多個(gè)進(jìn)程,如瀏覽器的tab標(biāo)簽頁(yè),一個(gè)進(jìn)程中的任務(wù)被劃分到多個(gè)線程處理,有GUI渲染線程,JS引擎線程,網(wǎng)絡(luò)線程等,JS的單線程即是指瀏覽器渲染進(jìn)程中的JS引擎線程(因?yàn)橹挥幸粋€(gè)JS引擎線程)。

了解了JS的單線程特性之后,我們來(lái)思考幾個(gè)問(wèn)題。

javascript為什么會(huì)是單線程的語(yǔ)言?

Javascript的單線程,與它的用途有關(guān)。作為瀏覽器腳本語(yǔ)言,Javascript的主要用途是與用戶互動(dòng),以及操作DOM。 在《javascript高級(jí)程序設(shè)計(jì)》一書(shū)中有一個(gè)很好的解釋:如果JS是多線程語(yǔ)言,那么假如當(dāng)多個(gè)線程同時(shí)操作同一個(gè)DOM的時(shí)候,瀏覽器該如何渲染?瀏覽器該聽(tīng)哪個(gè)線程的指令?渲染結(jié)果是否會(huì)超出預(yù)期?基于這個(gè)特性,JS必須只能是單線程語(yǔ)言。

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

Javascript代碼是如何執(zhí)行的?

Javascript并不是一行一行的分析并執(zhí)行代碼的,所有的 JS 代碼在運(yùn)行時(shí)都是在執(zhí)行上下文中進(jìn)行的。執(zhí)行上下文是一個(gè)抽象的概念,JS 中有三種執(zhí)行上下文:

  • 全局執(zhí)行上下文,默認(rèn)的,在瀏覽器中是 window 對(duì)象,并且 this 在非嚴(yán)格模式下指向它

  • 函數(shù)執(zhí)行上下文,JS 的函數(shù)每當(dāng)被調(diào)用時(shí)會(huì)創(chuàng)建一個(gè)上下文

  • Eval 執(zhí)行上下文,eval 函數(shù)會(huì)產(chǎn)生自己的上下文,這里不討論

執(zhí)行上下文在執(zhí)行棧(調(diào)用棧)中被以后進(jìn)先出的順序執(zhí)行。當(dāng)引擎第一次遇到 JS 代碼時(shí),會(huì)產(chǎn)生一個(gè)全局執(zhí)行上下文并壓入執(zhí)行棧,每遇到一個(gè)函數(shù)調(diào)用,就會(huì)往棧中壓入一個(gè)新的函數(shù)執(zhí)行上下文。引擎執(zhí)行棧頂的函數(shù)(執(zhí)行上下文),執(zhí)行完畢,彈出當(dāng)前執(zhí)行上下文,并等待垃圾回收,全局上下文只有唯一的一個(gè),它在瀏覽器關(guān)閉時(shí)出棧。

  • 棧,是一種數(shù)據(jù)結(jié)構(gòu),具有先進(jìn)后出的原則。JS 中的執(zhí)行棧就具有這樣的結(jié)構(gòu)。

  • 遞歸函數(shù)對(duì)函數(shù)的每次遞歸調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文,這意味著每次函數(shù)遞歸時(shí),都需要更多內(nèi)存來(lái)創(chuàng)建新上下文。

如何理解同步和異步?

  • 同步任務(wù): 指的是在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)。可以理解為在執(zhí)行完一個(gè)函數(shù)或方法之后,一直等待系統(tǒng)返回值或消息,這時(shí)程序是處于阻塞的,只有接收到返回的值或消息后才往下執(zhí)行其他的命令。

  • 異步任務(wù):不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

舉個(gè)例子:你在燒水的時(shí)候還可以去洗菜切菜,因?yàn)闊阒恍枰蜷_(kāi)開(kāi)關(guān)然后等水自己燒開(kāi)提醒你就好了,不需要一直等著燒水什么都不做,這里的燒水就是異步任務(wù)。

為什么是異步、并發(fā)、非阻塞的?

我們?cè)陧?yè)面中通常會(huì)發(fā)大量的請(qǐng)求,獲取后端的數(shù)據(jù)去渲染頁(yè)面。因?yàn)闉g覽器是單線程的,試想一下,當(dāng)我們發(fā)出異步請(qǐng)求的時(shí)候,阻塞了,后面的代碼都不執(zhí)行了,那頁(yè)面可能出現(xiàn)長(zhǎng)時(shí)間白屏極度影響用戶體驗(yàn)

所以JS采取了"異步任務(wù)回調(diào)通知"的模式,而實(shí)現(xiàn)這個(gè)“通知”的,正是事件循環(huán),當(dāng)遇到異步任務(wù)時(shí),就將這個(gè)任務(wù)交給對(duì)應(yīng)的線程,當(dāng)這個(gè)異步任務(wù)滿足回調(diào)條件時(shí),對(duì)應(yīng)的線程又通過(guò)事件觸發(fā)線程將這個(gè)事件放入任務(wù)隊(duì)列,然后主線程從任務(wù)隊(duì)列取出事件繼續(xù)執(zhí)行。

  • 事件循環(huán)并不是Javascript首創(chuàng)的,它是計(jì)算機(jī)的一種運(yùn)行機(jī)制。

  • 基于JS的用途是瀏覽器腳本語(yǔ)言,用于操作DOM與用戶進(jìn)行交互,為了避免多個(gè)線程同時(shí)操作DOM導(dǎo)致渲染結(jié)果超出預(yù)期,所以JS被設(shè)計(jì)為一個(gè)單線程的語(yǔ)言。

  • 開(kāi)發(fā)時(shí)會(huì)有很多耗時(shí)的異步任務(wù),如果都在主線程中阻塞,那會(huì)極度影響用戶體驗(yàn),所以JS是異步、并發(fā)、非阻塞的。

  • Javascript代碼的執(zhí)行過(guò)程中,依靠函數(shù)調(diào)用棧來(lái)搞定函數(shù)的執(zhí)行順序。

說(shuō)了這么多,終于輪到我們的主角了,下面有請(qǐng)任務(wù)隊(duì)列和事件循環(huán)登場(chǎng)。

任務(wù)隊(duì)列和事件循環(huán)

事件循環(huán)與任務(wù)隊(duì)列是JS中比較重要的兩個(gè)概念。這兩個(gè)概念在ES5和ES6兩個(gè)標(biāo)準(zhǔn)中有不同的實(shí)現(xiàn)。

ES5下的概念:
任務(wù)隊(duì)列是一個(gè)事件的隊(duì)列,所謂任務(wù)是WebAPIs返回的一個(gè)個(gè)通知,也可以理解成消息的隊(duì)列、回調(diào)隊(duì)列,里面存放異步任務(wù)的回調(diào),各個(gè)異步線程調(diào)用webAPI執(zhí)行完后通過(guò)事件觸發(fā)線程把回調(diào)函數(shù)放入任務(wù)隊(duì)列,表示相關(guān)的異步任務(wù)可以進(jìn)入“執(zhí)行棧”了,等待被主線程讀取

  • 一個(gè)事件循環(huán)中可以有多個(gè)任務(wù)隊(duì)列(task queue),一個(gè)任務(wù)隊(duì)列便是一系列有序任務(wù)(task)的集合

瀏覽器包含3類事件循環(huán):Window (用于運(yùn)行網(wǎng)頁(yè)內(nèi)容的瀏覽器級(jí)容器,包括實(shí)際的 window,一個(gè) tab 標(biāo)簽或者一個(gè) frame。)事件循環(huán)、Worker 事件循環(huán)、Worklet 事件循環(huán)

  • 隊(duì)列里的每個(gè)任務(wù)都有一個(gè)任務(wù)源(task source),源自同一個(gè)任務(wù)源的 task 必須放到同一個(gè)任務(wù)隊(duì)列,從不同源來(lái)的則被添加到不同隊(duì)列。

  • 同一個(gè)任務(wù)隊(duì)列中的任務(wù)必須按先進(jìn)先出的順序執(zhí)行,但是不保證多個(gè)任務(wù)隊(duì)列中的任務(wù)優(yōu)先級(jí),具體實(shí)現(xiàn)可能會(huì)交叉執(zhí)行,進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)(回調(diào)本身)

setTimeout/Ajax/Promise/DOM事件(user interaction task source) 等都是任務(wù)源,來(lái)自同類任務(wù)源的任務(wù)我們稱它們是同源的,比如setTimeout與setInterval就是同源的。

ES5中的事件循環(huán),如圖:

圖中有三大塊:

  • 函數(shù)調(diào)用棧:即執(zhí)行棧。

  • WebAPIs: 瀏覽器的接口,上面所說(shuō)的瀏覽器的對(duì)應(yīng)線程會(huì)使用這些接口處理,把它們放到相應(yīng)的任務(wù)隊(duì)列中。

  • 任務(wù)隊(duì)列們: 主線程有多個(gè)任務(wù)隊(duì)列,同源的任務(wù)被放入在屬于自己的任務(wù)隊(duì)列。

"任務(wù)隊(duì)列"遵循先進(jìn)先出的原則,排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過(guò)程基本上是自動(dòng)的,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程執(zhí)行。

主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))。

事件循環(huán)的大體流程:

  1. 主線程開(kāi)始執(zhí)行script代碼,同步代碼直接執(zhí)行,遇到異步任務(wù)源就將它掛起交給對(duì)應(yīng)的異步線程,自己繼續(xù)執(zhí)行同步任務(wù)

  2. 異步線程調(diào)用相應(yīng)API處理,滿足回調(diào)條件后,將異步回調(diào)事件放入任務(wù)隊(duì)列

  3. 主線程的執(zhí)行棧中的同步任務(wù)都執(zhí)行完畢后,就來(lái)讀取任務(wù)隊(duì)列中的異步任務(wù)回調(diào)事件

  4. 主線程不斷循環(huán)上述流程

到了ES6 的標(biāo)準(zhǔn),由于出現(xiàn)了 Promise ,ES5 時(shí)代的"同步任務(wù)"與"異步任務(wù)"已經(jīng)沒(méi)有辦法解釋其中的原理,因此出現(xiàn)了 task 隊(duì)列與 job 隊(duì)列之分。

ES6將任務(wù)分為 宏任務(wù)(macrotask)微任務(wù)(microtask),在新ECMAscript標(biāo)準(zhǔn)中,它們被分別稱為 task 與 jobs ; 任務(wù)隊(duì)列則為宏任務(wù)隊(duì)列(Task Queue)和微任務(wù)隊(duì)列(Job Queue)。

事件循環(huán)由宏任務(wù)和在執(zhí)行宏任務(wù)期間產(chǎn)生的所有微任務(wù)組成。宏任務(wù)隊(duì)列可以有多個(gè),微任務(wù)隊(duì)列只有一個(gè),完成當(dāng)下的宏任務(wù)后,會(huì)立刻執(zhí)行所有在此期間入隊(duì)的微任務(wù)。

這種設(shè)計(jì)是為了給緊急任務(wù)一個(gè)插隊(duì)的機(jī)會(huì),否則新入隊(duì)的任務(wù)永遠(yuǎn)被放在隊(duì)尾。微任務(wù)使得我們能夠在重新渲染UI之前執(zhí)行指定的行為,避免不必要的UI重繪。

TIPS: 其實(shí)并沒(méi)有宏任務(wù)隊(duì)列一說(shuō),人家原名就叫任務(wù)隊(duì)列(Task Queue)。首先要說(shuō)明宏任務(wù)其實(shí)一開(kāi)始就只是任務(wù)(task),因?yàn)镋S6新引入了Promise標(biāo)準(zhǔn),同時(shí)瀏覽器實(shí)現(xiàn)上多了一個(gè)microtask微任務(wù)概念,作為對(duì)照才稱宏任務(wù),至于宏任務(wù)隊(duì)列,為了便于理解和區(qū)分大家就這么叫了。

宏任務(wù)(task)

進(jìn)入執(zhí)行棧等待主線程執(zhí)行的主代碼塊,包括從異步隊(duì)列里加入到棧的,如setTimeout()、setInterval()的回調(diào),其中不含異步隊(duì)列中的微任務(wù)如Promise.then回調(diào)。

  • 宏任務(wù)大概包括:script(整塊代碼)setTimeoutsetIntervalI/ODOM事件(UI交互事件)setImmediate(node環(huán)境)、postMessageMessageChannel,這些也被稱作任務(wù)源

  • 宏任務(wù)是瀏覽器規(guī)定的(W3C)

  • 瀏覽器為了能夠使得JS內(nèi)部宏任務(wù)與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)宏任務(wù)執(zhí)行結(jié)束后,在下一個(gè)宏任務(wù)執(zhí)行開(kāi)始前,對(duì)頁(yè)面進(jìn)行重新渲染(GUI線程接管渲染,更新DOM樹(shù),重新繪制)

  • 異步任務(wù)可能是宏任務(wù)也可能是微任務(wù),而宏任務(wù)可能是異步代碼也可能是同步代碼,被掛起后放到任務(wù)隊(duì)列的是異步的宏任務(wù),同步宏任務(wù)會(huì)直接執(zhí)行

  • 宏任務(wù)隊(duì)列可以有多個(gè),微任務(wù)隊(duì)列只有一個(gè)

Q:有很多小伙伴不理解為什么“script(整塊代碼)”是宏任務(wù)

A: MDN文檔定義中有詳細(xì)說(shuō)明。

一個(gè)任務(wù)就是指計(jì)劃由標(biāo)準(zhǔn)機(jī)制來(lái)執(zhí)行的任何 Javascript,如程序的初始化、事件觸發(fā)的回調(diào)等。 除了使用事件,你還可以使用 setTimeout() 或者 setInterval() 來(lái)添加任務(wù)。

由此可以得出結(jié)論,宏任務(wù)包含js主代碼塊,但是有一個(gè)爭(zhēng)議存在,就是js主代碼塊是否進(jìn)入宏任務(wù)隊(duì)列中,或者說(shuō)任務(wù)隊(duì)列是否只存放異步任務(wù)回調(diào)關(guān)于這個(gè)問(wèn)題,目前主要存在兩種看法,

  1. script(整塊代碼)是宏任務(wù)(同步),首先被放入宏任務(wù)隊(duì)列中,一個(gè)事件循環(huán)從宏任務(wù)隊(duì)列開(kāi)始,開(kāi)始執(zhí)行時(shí)宏任務(wù)隊(duì)列中只有script(整塊代碼)任務(wù),遇到同步代碼直接入執(zhí)行棧執(zhí)行,異步代碼放入對(duì)應(yīng)的任務(wù)隊(duì)列。

  2. 沒(méi)有把 script(整塊代碼)放入宏任務(wù)隊(duì)列,而是直接被主線程壓入執(zhí)行棧執(zhí)行,只有異步任務(wù)才會(huì)被掛起并放入任務(wù)隊(duì)列。

我個(gè)人其實(shí)更傾向于第二種說(shuō)法,因?yàn)閹缀跛形恼露贾赋鋈蝿?wù)隊(duì)列是消息隊(duì)列、回調(diào)隊(duì)列,我是實(shí)在沒(méi)有找到script(整塊代碼)是怎么被放入或者是以什么形式被放入任務(wù)隊(duì)列的相關(guān)說(shuō)明,但其實(shí)這兩種說(shuō)法在實(shí)際代碼運(yùn)行表現(xiàn)上都是一致的,所以你怎么理解并不影響后續(xù)的事件循環(huán)流程,大家如果找到更官方更明確的說(shuō)法歡迎交流,解惑。

微任務(wù)

可以理解是在當(dāng)前宏任務(wù)執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)(宏任務(wù)的小跟班),也就是說(shuō),在當(dāng)前宏任務(wù)后,下一個(gè)宏任務(wù)之前,在重新渲染之前

宏任務(wù)->所有微任務(wù)->渲染,宏任務(wù)->所有微任務(wù)->渲染 ,...

微任務(wù)大概包括:new promise().then(回調(diào))MutationObserver(html5新特性)、Object.observe(已廢棄,proxy替代)、process.nextTick(node環(huán)境),這些也被稱作任務(wù)源

  • 執(zhí)行宏任務(wù)的過(guò)程中如果遇到微任務(wù),就把微任務(wù)放到微任務(wù)隊(duì)列,這個(gè)過(guò)程由主線程維護(hù),而非事件觸發(fā)線程

  • 當(dāng)執(zhí)行到script腳本的時(shí)候,js引擎會(huì)為全局創(chuàng)建一個(gè)執(zhí)行上下文,在該執(zhí)行上下文中維護(hù)了一個(gè)微任務(wù)隊(duì)列,這個(gè)微任務(wù)隊(duì)列是給 V8 引擎 內(nèi)部使用的,所以你是無(wú)法通過(guò) Javascript 直接訪問(wèn)的。

  • process.nextTick不在Event Loop的任何階段,他是一個(gè)特殊API,他會(huì)立即執(zhí)行,然后才會(huì)繼續(xù)執(zhí)行Event Loop,若同時(shí)存在promise和nextTick,則先執(zhí)行nextTick

區(qū)別:

任務(wù)隊(duì)列和微任務(wù)隊(duì)列的區(qū)別很簡(jiǎn)單,但卻很重要:
1.當(dāng)執(zhí)行來(lái)自任務(wù)隊(duì)列中的任務(wù)時(shí),在每一次新的事件循環(huán)開(kāi)始迭代的時(shí)候運(yùn)行時(shí)都會(huì)執(zhí)行隊(duì)列中的每個(gè)任務(wù)。在每次迭代開(kāi)始之后加入到隊(duì)列中的任務(wù)需要在下一次迭代開(kāi)始之后才會(huì)被執(zhí)行. 2.每次當(dāng)一個(gè)任務(wù)退出且執(zhí)行上下文為空的時(shí)候,微任務(wù)隊(duì)列中的每一個(gè)微任務(wù)會(huì)依次被執(zhí)行。不同的是它會(huì)等到微任務(wù)隊(duì)列為空才會(huì)停止執(zhí)行——即使中途有微任務(wù)加入。換句話說(shuō),微任務(wù)可以添加新的微任務(wù)到隊(duì)列中,并在下一個(gè)任務(wù)開(kāi)始執(zhí)行之前且當(dāng)前事件循環(huán)結(jié)束之前執(zhí)行完所有的微任務(wù)

簡(jiǎn)單概括一下區(qū)別

  • 宏任務(wù)隊(duì)列一次循環(huán)執(zhí)行一個(gè)宏任務(wù),后面的宏任務(wù)下個(gè)循環(huán)執(zhí)行,微任務(wù)隊(duì)列一次循環(huán)執(zhí)行所有微任務(wù),即清空微任務(wù)隊(duì)列

  • 微任務(wù)可以添加新的微任務(wù)到隊(duì)列中,中途插隊(duì)執(zhí)行

  • 宏任務(wù)中的事件放在宏任務(wù)隊(duì)列中,由事件觸發(fā)線程維護(hù);微任務(wù)的事件放在微任務(wù)隊(duì)列中,由js引擎線程(主線程)維護(hù)

了解了宏任務(wù)和微任務(wù)的概念之后,我們來(lái)補(bǔ)充一下ES6事件循環(huán)的具體流程:

  1. 首先,javascript整體代碼被作為宏任務(wù)放入執(zhí)行棧中執(zhí)行,所有同步代碼先執(zhí)行,執(zhí)行過(guò)程中,當(dāng)遇到任務(wù)源時(shí),判斷是宏任務(wù)還是微任務(wù)

  2. 如果是宏任務(wù),加入到宏任務(wù)隊(duì)列中,如果是微任務(wù),加入到微任務(wù)隊(duì)列中

  3. 同步代碼執(zhí)行完成后,執(zhí)行棧空閑,檢查微任務(wù)隊(duì)列中是否有可執(zhí)行任務(wù),如果有,依次執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù)

  4. 渲染UI,開(kāi)始下一輪循環(huán)

  5. 檢查宏任務(wù)隊(duì)列是否有可執(zhí)行的宏任務(wù),如果有,取出隊(duì)列中最前面的那個(gè)宏任務(wù),加入到執(zhí)行棧中開(kāi)始執(zhí)行,然后重復(fù)以上步驟,直到宏任務(wù)隊(duì)列中所有任務(wù)執(zhí)行結(jié)束

定時(shí)器不準(zhǔn)

任務(wù)隊(duì)列可以放置定時(shí)器回調(diào)事件,但是需要注意的是,setTimeout()只是將事件插入了"任務(wù)隊(duì)列",必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時(shí)很長(zhǎng),有可能要等很久,所以并沒(méi)有辦法保證,回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行。

假設(shè)我們定義了一個(gè)2s的定時(shí)器,那么該定時(shí)器的執(zhí)行流程如下:

  1. 主線程執(zhí)行同步代碼

  2. 遇到setTimeout,將它交給定時(shí)器線程

  3. 定時(shí)器線程開(kāi)始計(jì)時(shí),2秒到了通知事件觸發(fā)線程

  4. 事件觸發(fā)線程將定時(shí)器回調(diào)放入事件隊(duì)列,異步流程到此結(jié)束

  5. 主線程如果有空,將定時(shí)器回調(diào)拿出來(lái)執(zhí)行,如果沒(méi)空這個(gè)回調(diào)就一直放在隊(duì)列里。

所以,如果在定義了定時(shí)器之后,我們又進(jìn)行了非常耗時(shí)的同步代碼運(yùn)算,那即使到了2s,同步代碼也會(huì)阻塞定時(shí)器回調(diào)事件的執(zhí)行,因此,此時(shí)回調(diào)執(zhí)行的時(shí)間必然是不準(zhǔn)確的了,所以再次強(qiáng)調(diào),寫(xiě)代碼時(shí)一定不要長(zhǎng)時(shí)間占用主線程

事件循環(huán)總結(jié)

事件循環(huán)(Event Loop) 是讓 Javascript 做到既是單線程,又絕對(duì)不會(huì)阻塞的核心機(jī)制,也是 Javascript 并發(fā)模型的基礎(chǔ),是用來(lái)協(xié)調(diào)各種事件、用戶交互、腳本執(zhí)行、UI 渲染、網(wǎng)絡(luò)請(qǐng)求等的一種機(jī)制,具體的管理方法由它所處的運(yùn)行環(huán)境決定,目前JS的主要運(yùn)行環(huán)境有兩個(gè),瀏覽器和Node.js,這兩個(gè)環(huán)境的事件循環(huán)機(jī)制還有些區(qū)別,Node.js的事件循環(huán)我之后會(huì)另開(kāi)一篇文章細(xì)說(shuō)。

  1. 事件循環(huán)是讓 JS 做到既是單線程,又可以異步并發(fā)不會(huì)阻塞的核心機(jī)制。

  2. 瀏覽器是不僅是多進(jìn)程而且是多線程的,如渲染進(jìn)程中有GUI渲染線程、JS引擎線程、計(jì)時(shí)器線程、HTTP請(qǐng)求線程、事件觸發(fā)線程,事件循環(huán)就是依靠瀏覽器底層的多線程實(shí)現(xiàn),所謂JS的單線程指的就是瀏覽器渲染進(jìn)程中的JS引擎線程,因?yàn)橹挥幸粋€(gè)JS引擎線程,所以是單線程,也被稱為主線程。

  3. 主線程執(zhí)行JS代碼的過(guò)程中,依靠執(zhí)行棧來(lái)管理執(zhí)行任務(wù)的順序,遵循后進(jìn)先出的原則,同步任務(wù)直接入棧執(zhí)行,異步任務(wù)被掛起待完成后被放入任務(wù)隊(duì)列,

  4. 任務(wù)隊(duì)列有宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列的區(qū)別,宏任務(wù)隊(duì)列中存放宏任務(wù),如setTimeout、setInterval、DOM事件等,微任務(wù)隊(duì)列中存放微任務(wù),如Promise的then回調(diào)等。

  5. 當(dāng)執(zhí)行棧的任務(wù)執(zhí)行完成后會(huì)去讀取任務(wù)隊(duì)列中的任務(wù),優(yōu)先執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù),微任務(wù)隊(duì)列清空后,重新渲染UI,開(kāi)始下一輪循環(huán),檢查宏任務(wù)隊(duì)列是否有可執(zhí)行的宏任務(wù),如果有,取出隊(duì)列中最前面的那個(gè)宏任務(wù),加入到執(zhí)行棧中開(kāi)始執(zhí)行,重復(fù)以上步驟就是事件循環(huán)。

參考文檔




該文章在 2023/7/29 10:10:18 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
日本码亚洲成a人片 | 亚洲日本人成网 | 三级国产国语三级在线2 | 日本A网免费在线观看 | 脸国产精品自产拍在线观看 | 在线看片国产日韩欧美亚洲 |