這篇文章將為大家詳細講解有關JS中瀏覽器事件循環機制的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
先來明白些概念性內容。
進程、線程
進程是系統分配的獨立資源,是 CPU 資源分配的基本單位,進程是由一個或者多個線程組成的。
線程是進程的執行流,是CPU調度和分派的基本單位,同個進程之中的多個線程之間是共享該進程的資源的。
瀏覽器內核
瀏覽器是多進程的,瀏覽器每一個 tab 標簽都代表一個獨立的進程(也不一定,因為多個空白 tab 標簽會合并成一個進程),瀏覽器內核(瀏覽器渲染進程)屬于瀏覽器多進程中的一種。
瀏覽器內核有多種線程在工作。
GUI 渲染線程:
負責渲染頁面,解析 HTML,CSS 構成 DOM 樹等,當頁面重繪或者由于某種操作引起回流都會調起該線程。
和 JS 引擎線程是互斥的,當 JS 引擎線程在工作的時候,GUI 渲染線程會被掛起,GUI 更新被放入在 JS 任務隊列中,等待 JS 引擎線程空閑的時候繼續執行。
JS 引擎線程:
單線程工作,負責解析運行 JavaScript 腳本。
和 GUI 渲染線程互斥,JS 運行耗時過長就會導致頁面阻塞。
事件觸發線程:
當事件符合觸發條件被觸發時,該線程會把對應的事件回調函數添加到任務隊列的隊尾,等待 JS 引擎處理。
定時器觸發線程:
瀏覽器定時計數器并不是由 JS 引擎計數的,阻塞會導致計時不準確。
開啟定時器觸發線程來計時并觸發計時,計時完成后會被添加到任務隊列中,等待 JS 引擎處理。
http 請求線程:
http 請求的時候會開啟一條請求線程。
請求完成有結果了之后,將請求的回調函數添加到任務隊列中,等待 JS 引擎處理。

JavaScript 引擎是單線程
JavaScript 引擎是單線程,也就是說每次只能執行一項任務,其他任務都得按照順序排隊等待被執行,只有當前的任務執行完成之后才會往下執行下一個任務。
HTML5 中提出了 Web-Worker API,主要是為了解決頁面阻塞問題,但是并沒有改變 JavaScript 是單線程的本質。了解 Web-Worker。
JavaScript 事件循環機制
JavaScript 事件循環機制分為瀏覽器和 Node 事件循環機制,兩者的實現技術不一樣,瀏覽器 Event Loop 是 HTML 中定義的規范,Node Event Loop 是由 libuv 庫實現。這里主要講的是瀏覽器部分。
Javascript 有一個 main thread 主線程和 call-stack 調用棧(執行棧),所有的任務都會被放到調用棧等待主線程執行。
JS 調用棧
JS 調用棧是一種后進先出的數據結構。當函數被調用時,會被添加到棧中的頂部,執行完成之后就從棧頂部移出該函數,直到棧內被清空。
同步任務、異步任務
JavaScript 單線程中的任務分為同步任務和異步任務。同步任務會在調用棧中按照順序排隊等待主線程執行,異步任務則會在異步有了結果后將注冊的回調函數添加到任務隊列(消息隊列)中等待主線程空閑的時候,也就是棧內被清空的時候,被讀取到棧中等待主線程執行。任務隊列是先進先出的數據結構。
Event Loop
調用棧中的同步任務都執行完畢,棧內被清空了,就代表主線程空閑了,這個時候就會去任務隊列中按照順序讀取一個任務放入到棧中執行。每次棧內被清空,都會去讀取任務隊列有沒有任務,有就讀取執行,一直循環讀取-執行的操作,就形成了事件循環。


定時器
定時器會開啟一條定時器觸發線程來觸發計時,定時器會在等待了指定的時間后將事件放入到任務隊列中等待讀取到主線程執行。
定時器指定的延時毫秒數其實并不準確,因為定時器只是在到了指定的時間時將事件放入到任務隊列中,必須要等到同步的任務和現有的任務隊列中的事件全部執行完成之后,才會去讀取定時器的事件到主線程執行,中間可能會存在耗時比較久的任務,那么就不可能保證在指定的時間執行。
宏任務(macro-task)、微任務(micro-task)
除了廣義的同步任務和異步任務,JavaScript 單線程中的任務可以細分為宏任務和微任務。
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被稱為任務源,來自不同的任務源注冊的回調函數會被放入到不同的任務隊列中。
有了宏任務和微任務的概念后,那 JS 的執行順序是怎樣的?是宏任務先還是微任務先?
第一次事件循環中,JavaScript 引擎會把整個 script 代碼當成一個宏任務執行,執行完成之后,再檢測本次循環中是否尋在微任務,存在的話就依次從微任務的任務隊列中讀取執行完所有的微任務,再讀取宏任務的任務隊列中的任務執行,再執行所有的微任務,如此循環。JS 的執行順序就是每次事件循環中的宏任務-微任務。
上面的示例中,第一次事件循環,整段代碼作為宏任務進入主線程執行。
遇到了 setTimeout ,就會等到過了指定的時間后將回調函數放入到宏任務的任務隊列中。
遇到 Promise,將 then 函數放入到微任務的任務隊列中。
整個事件循環完成之后,會去檢測微任務的任務隊列中是否存在任務,存在就執行。
第一次的循環結果打印為: 1,3,5,4。
接著再到宏任務的任務隊列中按順序取出一個宏任務到棧中讓主線程執行,那么在這次循環中的宏任務就是 setTimeout 注冊的回調函數,執行完這個回調函數,發現在這次循環中并不存在微任務,就準備進行下一次事件循環。
檢測到宏任務隊列中已經沒有了要執行的任務,那么就結束事件循環。
最終的結果就是 1,3,5,4,2。
關于“JS中瀏覽器事件循環機制的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。