摘要:檢查宏任務隊列,發現有的回調函數立即執行回調函數輸出。接著遇到它的作用是在后將回調函數放到宏任務隊列中這個任務在再下一次的事件循環中執行。
為什么會寫這篇博文呢?
前段時間,和頭條的小伙伴聊天問頭條面試前端會問哪些問題,他稱如果是他面試的話,event-loop肯定是要問的。那天聊了蠻多,event-loop算是給我留下了很深的印象,原因很簡單,因為之前我從未深入了解過,如果是面試的時候,我遇到了這個問題,估計回答得肯定不如人意。
因此,最近我閱讀了一些相關的文章,并細細梳理了一番,輸出了本篇博文,希望能幫助大家搞懂瀏覽器的event-loop。后續會補充node中的event-loop。
1. 預備知識JavaScript的運行機制:
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步
概括即是: 調用棧中的同步任務都執行完畢,棧內被清空了,就代表主線程空閑了,這個時候就會去任務隊列中按照順序讀取一個任務放入到棧中執行。每次棧內被清空,都會去讀取任務隊列有沒有任務,有就讀取執行,一直循環讀取-執行的操作
一個事件循環中有一個或者是多個任務隊列
JavaScript中有兩種異步任務:
宏任務: script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
微任務: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;
2. 事件循環(event-loop)是什么?主線程從"任務隊列"中讀取執行事件,這個過程是循環不斷的,這個機制被稱為事件循環。此機制具體如下:主線程會不斷從任務隊列中按順序取任務執行,每執行完一個任務都會檢查microtask隊列是否為空(執行完一個任務的具體標志是函數執行棧為空),如果不為空則會一次性執行完所有microtask。然后再進入下一個循環去任務隊列中取下一個任務執行。
詳細說明:
選擇當前要執行的宏任務隊列,選擇一個最先進入任務隊列的宏任務,如果沒有宏任務可以選擇,則會跳轉至microtask的執行步驟。
將事件循環的當前運行宏任務設置為已選擇的宏任務。
運行宏任務。
將事件循環的當前運行任務設置為null。
將運行完的宏任務從宏任務隊列中移除。
microtasks步驟:進入microtask檢查點。
更新界面渲染。
返回第一步。
執行進入microtask檢查的的具體步驟如下:
設置進入microtask檢查點的標志為true。
當事件循環的微任務隊列不為空時:選擇一個最先進入microtask隊列的microtask;設置事件循環的當前運行任務為已選擇的microtask;運行microtask;設置事件循環的當前運行任務為null;將運行結束的microtask從microtask隊列中移除。
對于相應事件循環的每個環境設置對象(environment settings object),通知它們哪些promise為rejected。
清理indexedDB的事務。
設置進入microtask檢查點的標志為false。
需要注意的是:當前執行棧執行完畢時會立刻先處理所有微任務隊列中的事件,然后再去宏任務隊列中取出一個事件。同一次事件循環中,微任務永遠在宏任務之前執行。
圖示:
3. Event-loop 是如何工作的?先看一個簡單的示例:
setTimeout(()=>{ console.log("setTimeout1"); Promise.resolve().then(data => { console.log(222); }); }); setTimeout(()=>{ console.log("setTimeout2"); }); Promise.resolve().then(data=>{ console.log(111); });
思考一下, 運行結果是什么?
運行結果為:
111 setTimeout1 222 setTimeout2
我們來看一下為什么?
我們來詳細說明一下, JS引擎是如何執行這段代碼的:
主線程上沒有需要執行的代碼
接著遇到setTimeout 0,它的作用是在 0ms 后將回調函數放到宏任務隊列中(這個任務在下一次的事件循環中執行)。
接著遇到setTimeout 0,它的作用是在 0ms 后將回調函數放到宏任務隊列中(這個任務在再下一次的事件循環中執行)。
首先檢查微任務隊列, 即 microtask隊列,發現此隊列不為空,執行第一個promise的then回調,輸出 "111"。
此時microtask隊列為空,進入下一個事件循環, 檢查宏任務隊列,發現有 setTimeout的回調函數,立即執行回調函數輸出 "setTimeout1",檢查microtask 隊列,發現隊列不為空,執行promise的then回調,輸出"222",microtask隊列為空,進入下一個事件循環。
檢查宏任務隊列,發現有 setTimeout的回調函數, 立即執行回調函數輸出"setTimeout2"。
再思考一下下面代碼的執行順序:
console.log("script start"); setTimeout(function () { console.log("setTimeout---0"); }, 0); setTimeout(function () { console.log("setTimeout---200"); setTimeout(function () { console.log("inner-setTimeout---0"); }); Promise.resolve().then(function () { console.log("promise5"); }); }, 200); Promise.resolve().then(function () { console.log("promise1"); }).then(function () { console.log("promise2"); }); Promise.resolve().then(function () { console.log("promise3"); }); console.log("script end");
思考一下,運行結果是什么?
運行結果為:
script start script end promise1 promise3 promise2 setTimeout---0 setTimeout---200 promise5 inner-setTimeout---0
那么為什么?
我們來詳細說明一下,JS引擎是如何執行這段代碼的:
首先順序執行完主進程上的同步任務,第一句和最后一句的console.log
接著遇到setTimeout 0,它的作用是在 0ms 后將回調函數放到宏任務隊列中(這個任務在下一次的事件循環中執行)。
接著遇到setTimeout 200,它的作用是在 200ms 后將回調函數放到宏任務隊列中(這個任務在再下一次的事件循環中執行)。
同步任務執行完之后,首先檢查微任務隊列,即 microtask隊列, 發現此隊列不為空,執行第一個promise的then回調,輸出 "promise1",然后執行第二個promise的then回調,輸出"promise3",由于第一個promise的.then()的返回依然是promise,所以第二個.then()會放到microtask隊列繼續執行,輸出 "promise2";
此時microtask隊列為空,進入下一個事件循環,檢查宏任務隊列,發現有 setTimeout的回調函數,立即執行回調函數輸出 "setTimeout---0",檢查microtask 隊列,隊列為空,進入下一次事件循環.
檢查宏任務隊列,發現有 setTimeout的回調函數,立即執行回調函數輸出"setTimeout---200".
接著遇到setTimeout 0,它的作用是在 0ms 后將回調函數放到宏任務隊列中,檢查微任務隊列,即 microtask 隊列, 發現此隊列不為空,執行promise的then回調,輸出"promise5"。
此時microtask隊列為空,進入下一個事件循環,檢查宏任務隊列,發現有 setTimeout 的回調函數,立即執行回調函數輸出,輸出"inner-setTimeout---0".代碼執行結束.
4. 為什么會需要event-loop?因為 JavaScript 是單線程的。單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。為了協調事件(event),用戶交互(user interaction),腳本(script),渲染(rendering),網絡(networking)等,用戶代理(user agent)必須使用事件循環(event loops)。
5. 參考文章:https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
http://www.ruanyifeng.com/blo...
謝謝您花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,那么不要吝嗇你的贊和Star哈,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
關注小姐姐的公眾號,和小姐姐一起學前端。文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/101761.html
摘要:由于是單線程的,這些方法就會按順序被排列在一個單獨的地方,這個地方就是所謂執行棧。事件隊列每次僅執行一個任務,在該任務執行完畢之后,再執行下一個任務。 Event Loop 是 JavaScript 異步編程的核心思想,也是前端進階必須跨越的一關。同時,它又是面試的必考點,特別是在 Promise 出現之后,各種各樣的面試題層出不窮,花樣百出。這篇文章從現實生活中的例子入手,讓你徹底理解 E...
摘要:徹底搞懂通過瀏覽器的開發者工具可以直觀的看到,圖中藍色的線和藍色的字使用不同的表現形式表示這個事件觸發的時間。當腳本下載完后立即執行,執行順序不確定。 徹底搞懂 defer & async DOMContentLoaded showImg(https://segmentfault.com/img/remote/1460000013480394?w=1309&h=879); 通過 chr...
摘要:徹底搞懂執行機制首先我們大家都了解的是,是一門單線程語言,所以我們就可以得出是按照語句順序執行的首先看這個顯然大家都知道結果,依次輸出,然而換一種這個時候再看代碼的順序執行,輸出,,,。不過即使主線程為空,也是達不到的,根據標準,最低是。 徹底搞懂JavaScript執行機制 首先我們大家都了解的是,JavaScript 是一門單線程語言,所以我們就可以得出: JavaScript 是...
摘要:在單頁應用中,通常由前端來配置路由,根據不同的顯示不同的內容。接口是新增的,它有五個方法可以改變而不刷新頁面。事件能監聽除和外的變化。而模式下,我們不僅要在事件回調里處理的變化,還需要分別在和方法里處理的變化。 在單頁應用中,通常由前端來配置路由,根據不同的 url 顯示不同的內容。想要知道這是如何做到的,首先得了解瀏覽器提供的兩大 API: window.location lo...
閱讀 1010·2023-04-25 15:42
閱讀 3605·2021-11-02 14:38
閱讀 2899·2021-09-30 09:48
閱讀 1439·2021-09-23 11:22
閱讀 3400·2021-09-06 15:02
閱讀 3197·2021-09-04 16:41
閱讀 615·2021-09-02 15:41
閱讀 2025·2021-08-26 14:13