摘要:事件循環(huán)機制事件循環(huán)機制分為瀏覽器和事件循環(huán)機制,兩者的實現(xiàn)技術(shù)不一樣,瀏覽器是中定義的規(guī)范,是由庫實現(xiàn)。整個事件循環(huán)完成之后,會去檢測微任務(wù)的任務(wù)隊列中是否存在任務(wù),存在就執(zhí)行。
文章來自我的 github 博客,包括技術(shù)輸出和學習筆記,歡迎star。
先來明白些概念性內(nèi)容。
進程、線程進程是系統(tǒng)分配的獨立資源,是 CPU 資源分配的基本單位,進程是由一個或者多個線程組成的。
線程是進程的執(zhí)行流,是CPU調(diào)度和分派的基本單位,同個進程之中的多個線程之間是共享該進程的資源的。
瀏覽器內(nèi)核瀏覽器是多進程的,瀏覽器每一個 tab 標簽都代表一個獨立的進程(也不一定,因為多個空白 tab 標簽會合并成一個進程),瀏覽器內(nèi)核(瀏覽器渲染進程)屬于瀏覽器多進程中的一種。
瀏覽器內(nèi)核有多種線程在工作。
GUI 渲染線程:
負責渲染頁面,解析 HTML,CSS 構(gòu)成 DOM 樹等,當頁面重繪或者由于某種操作引起回流都會調(diào)起該線程。
和 JS 引擎線程是互斥的,當 JS 引擎線程在工作的時候,GUI 渲染線程會被掛起,GUI 更新被放入在 JS 任務(wù)隊列中,等待 JS 引擎線程空閑的時候繼續(xù)執(zhí)行。
JS 引擎線程:
單線程工作,負責解析運行 JavaScript 腳本。
和 GUI 渲染線程互斥,JS 運行耗時過長就會導致頁面阻塞。
事件觸發(fā)線程:
當事件符合觸發(fā)條件被觸發(fā)時,該線程會把對應(yīng)的事件回調(diào)函數(shù)添加到任務(wù)隊列的隊尾,等待 JS 引擎處理。
定時器觸發(fā)線程:
瀏覽器定時計數(shù)器并不是由 JS 引擎計數(shù)的,阻塞會導致計時不準確。
開啟定時器觸發(fā)線程來計時并觸發(fā)計時,計時完成后會被添加到任務(wù)隊列中,等待 JS 引擎處理。
http 請求線程:
http 請求的時候會開啟一條請求線程。
請求完成有結(jié)果了之后,將請求的回調(diào)函數(shù)添加到任務(wù)隊列中,等待 JS 引擎處理。
JavaScript 引擎是單線程JavaScript 引擎是單線程,也就是說每次只能執(zhí)行一項任務(wù),其他任務(wù)都得按照順序排隊等待被執(zhí)行,只有當前的任務(wù)執(zhí)行完成之后才會往下執(zhí)行下一個任務(wù)。
HTML5 中提出了 Web-Worker API,主要是為了解決頁面阻塞問題,但是并沒有改變 JavaScript 是單線程的本質(zhì)。了解 Web-Worker。
JavaScript 事件循環(huán)機制JavaScript 事件循環(huán)機制分為瀏覽器和 Node 事件循環(huán)機制,兩者的實現(xiàn)技術(shù)不一樣,瀏覽器 Event Loop 是 HTML 中定義的規(guī)范,Node Event Loop 是由 libuv 庫實現(xiàn)。這里主要講的是瀏覽器部分。
Javascript 有一個 main thread 主線程和 call-stack 調(diào)用棧(執(zhí)行棧),所有的任務(wù)都會被放到調(diào)用棧等待主線程執(zhí)行。
JS 調(diào)用棧
JS 調(diào)用棧是一種后進先出的數(shù)據(jù)結(jié)構(gòu)。當函數(shù)被調(diào)用時,會被添加到棧中的頂部,執(zhí)行完成之后就從棧頂部移出該函數(shù),直到棧內(nèi)被清空。
同步任務(wù)、異步任務(wù)
JavaScript 單線程中的任務(wù)分為同步任務(wù)和異步任務(wù)。同步任務(wù)會在調(diào)用棧中按照順序排隊等待主線程執(zhí)行,異步任務(wù)則會在異步有了結(jié)果后將注冊的回調(diào)函數(shù)添加到任務(wù)隊列(消息隊列)中等待主線程空閑的時候,也就是棧內(nèi)被清空的時候,被讀取到棧中等待主線程執(zhí)行。任務(wù)隊列是先進先出的數(shù)據(jù)結(jié)構(gòu)。
Event Loop
調(diào)用棧中的同步任務(wù)都執(zhí)行完畢,棧內(nèi)被清空了,就代表主線程空閑了,這個時候就會去任務(wù)隊列中按照順序讀取一個任務(wù)放入到棧中執(zhí)行。每次棧內(nèi)被清空,都會去讀取任務(wù)隊列有沒有任務(wù),有就讀取執(zhí)行,一直循環(huán)讀取-執(zhí)行的操作,就形成了事件循環(huán)。
定時器
定時器會開啟一條定時器觸發(fā)線程來觸發(fā)計時,定時器會在等待了指定的時間后將事件放入到任務(wù)隊列中等待讀取到主線程執(zhí)行。
定時器指定的延時毫秒數(shù)其實并不準確,因為定時器只是在到了指定的時間時將事件放入到任務(wù)隊列中,必須要等到同步的任務(wù)和現(xiàn)有的任務(wù)隊列中的事件全部執(zhí)行完成之后,才會去讀取定時器的事件到主線程執(zhí)行,中間可能會存在耗時比較久的任務(wù),那么就不可能保證在指定的時間執(zhí)行。
宏任務(wù)(macro-task)、微任務(wù)(micro-task)
除了廣義的同步任務(wù)和異步任務(wù),JavaScript 單線程中的任務(wù)可以細分為宏任務(wù)和微任務(wù)。
macro-task包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。
console.log(1); setTimeout(function() { console.log(2); }) var promise = new Promise(function(resolve, reject) { console.log(3); resolve(); }) promise.then(function() { console.log(4); }) console.log(5);
示例中,setTimeout 和 Promise被稱為任務(wù)源,來自不同的任務(wù)源注冊的回調(diào)函數(shù)會被放入到不同的任務(wù)隊列中。
有了宏任務(wù)和微任務(wù)的概念后,那 JS 的執(zhí)行順序是怎樣的?是宏任務(wù)先還是微任務(wù)先?
第一次事件循環(huán)中,JavaScript 引擎會把整個 script 代碼當成一個宏任務(wù)執(zhí)行,執(zhí)行完成之后,再檢測本次循環(huán)中是否尋在微任務(wù),存在的話就依次從微任務(wù)的任務(wù)隊列中讀取執(zhí)行完所有的微任務(wù),再讀取宏任務(wù)的任務(wù)隊列中的任務(wù)執(zhí)行,再執(zhí)行所有的微任務(wù),如此循環(huán)。JS 的執(zhí)行順序就是每次事件循環(huán)中的宏任務(wù)-微任務(wù)。
上面的示例中,第一次事件循環(huán),整段代碼作為宏任務(wù)進入主線程執(zhí)行。
遇到了 setTimeout ,就會等到過了指定的時間后將回調(diào)函數(shù)放入到宏任務(wù)的任務(wù)隊列中。
遇到 Promise,將 then 函數(shù)放入到微任務(wù)的任務(wù)隊列中。
整個事件循環(huán)完成之后,會去檢測微任務(wù)的任務(wù)隊列中是否存在任務(wù),存在就執(zhí)行。
第一次的循環(huán)結(jié)果打印為: 1,3,5,4。
接著再到宏任務(wù)的任務(wù)隊列中按順序取出一個宏任務(wù)到棧中讓主線程執(zhí)行,那么在這次循環(huán)中的宏任務(wù)就是 setTimeout 注冊的回調(diào)函數(shù),執(zhí)行完這個回調(diào)函數(shù),發(fā)現(xiàn)在這次循環(huán)中并不存在微任務(wù),就準備進行下一次事件循環(huán)。
檢測到宏任務(wù)隊列中已經(jīng)沒有了要執(zhí)行的任務(wù),那么就結(jié)束事件循環(huán)。
最終的結(jié)果就是 1,3,5,4,2。
參考https://juejin.im/post/59e85eebf265da430d571f89
https://juejin.im/post/59c25c936fb9a00a3f24e114
https://segmentfault.com/a/1190000012925872
https://zhuanlan.zhihu.com/p/26229293
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/95985.html
摘要:主線程不斷重復上面的三步,此過程也就是常說的事件循環(huán)。所以主線程代碼執(zhí)行時間過長,會阻塞事件循環(huán)的執(zhí)行。參考資料這一次,徹底弄懂執(zhí)行機制任務(wù)隊列的順序機制事件循環(huán)搞懂異步事件輪詢與中的事件循環(huán) 1. 說明 讀過本文章后,您能知道: JavaScript代碼在瀏覽器中的執(zhí)行機制和事件循環(huán) 面試中經(jīng)常遇到的代碼輸出順序問題 首先通過一段代碼來驗證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:主線程要明確的一點是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個事件。主線程循環(huán)即主線程會不停的從執(zhí)行棧中讀取事件,會執(zhí)行完所有棧中的同步代碼。以上參考資料詳解中的事件循環(huán)機制中的事件循環(huán)運行機制詳解再談 showImg(https://segmentfault.com/img/remote/1460000015317437?w=1920&h=1080); 前言 大家都...
摘要:如果沒有其他異步任務(wù)要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結(jié)果。執(zhí)行的執(zhí)行事件關(guān)閉請求的,例如事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過上述的階段。因此,才會早于執(zhí)行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(wù)(Synchronous) 在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù) ...
摘要:事件循環(huán)機制首先區(qū)分進程和線程進程是資源分配的最小單位系統(tǒng)會給它分配內(nèi)存不同的進程之間是可以同學的,如管道命名管道消息隊列一個進程里有單個或多個線程瀏覽器是多進程的,因為系統(tǒng)給它的進程分配了資源內(nèi)存打開會有一個主進程,每打開一個頁就有一個獨 JS JavaScript事件循環(huán)機制 首先區(qū)分進程和線程 進程是cpu資源分配的最小單位(系統(tǒng)會給它分配內(nèi)存) 不同的進程之間是可以同學的,如...
摘要:的單線程,與它的用途有關(guān)。特點的顯著特點異步機制事件驅(qū)動。隊列的讀取輪詢線程,事件的消費者,的主角。它將不同的任務(wù)分配給不同的線程,形成一個事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 這兩天跟同事同事討論遇到的一個問題,js中的event loop,引出了chrome與node中運行具有setTimeout和Promise的程序時候執(zhí)行結(jié)果不一樣的問題,從而引出了Nodejs的...
閱讀 4933·2021-11-25 09:43
閱讀 1186·2021-11-24 09:38
閱讀 1892·2021-09-30 09:54
閱讀 2800·2021-09-23 11:21
閱讀 2367·2021-09-10 10:51
閱讀 2368·2021-09-03 10:45
閱讀 1163·2019-08-30 15:52
閱讀 1766·2019-08-30 14:13