摘要:具體的可以用下面的圖來大致說明一下同步和異步任務分別進入不同的執行環境,同步的進入主線程,即主執行棧,異步的進入。主線程內的任務執行完畢為空,會去讀取對應的任務,推入主線程執行。
前言
眾所周知,JavaScript是一門單線程語言,雖然在html5中提出了Web-Worker,但這并未改變JavaScript是單線程這一核心。可看HTML規范中的這段話:
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.
為了協調事件、用戶交互、腳本、UI 渲染和網絡處理等行為,用戶引擎必須使用event loops。Event Loop包含兩類:一類是基于Browsing Context,一種是基于Worker,二者是獨立運行的。
下面本文用一個例子,著重講解下基于Browsing Context的事件循環機制。
來看下面這段JavaScript代碼:
console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); Promise.resolve().then(function() { console.log("promise1"); }).then(function() { console.log("promise2"); }); console.log("script end");
先猜測一下這段代碼的輸出順序是什么,再去瀏覽器控制臺輸入一下,看看實際輸出的順序和你猜測出的順序是否一致,如果一致,那就說明,你對JavaScript的事件循環機制還是有一定了解的,繼續往下看可以鞏固下你的知識;而如果實際輸出的順序和你的猜測不一致,那么本文下面的部分會為你答疑解惑。
任務隊列所有的任務可以分為同步任務和異步任務,同步任務,顧名思義,就是立即執行的任務,同步任務一般會直接進入到主線程中執行;而異步任務,就是異步執行的任務,比如ajax網絡請求,setTimeout定時函數等都屬于異步任務,異步任務會通過任務隊列(Event Queue)的機制來進行協調。具體的可以用下面的圖來大致說明一下:
同步和異步任務分別進入不同的執行環境,同步的進入主線程,即主執行棧,異步的進入Event Queue。主線程內的任務執行完畢為空,會去Event Queue讀取對應的任務,推入主線程執行。
上述過程的不斷重復就是我們說的Event Loop(事件循環)。
在事件循環中,每進行一次循環操作稱為tick,通過閱讀規范可知,每一次tick的任務處理模型是比較復雜的,其關鍵的步驟可以總結如下:
在此次tick中選擇最先進入隊列的任務(oldest task),如果有則執行(一次)
檢查是否存在Microtasks,如果存在則不停地執行,直至清空Microtask Queue
更新render
主線程重復執行上述步驟
可以用一張圖來說明下流程:
這里相信有人會想問,什么是microtasks?規范中規定,task分為兩大類, 分別是Macro Task (宏任務)和Micro Task(微任務), 并且每個宏任務結束后, 都要清空所有的微任務,這里的Macro Task也是我們常說的task,有些文章并沒有對其做區分,后面文章中所提及的task皆看做宏任務(macro task)。
(macro)task主要包含:script(整體代碼)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js 環境)
microtask主要包含:Promise、MutaionObserver、process.nextTick(Node.js 環境)
setTimeout/Promise等API便是任務源,而進入任務隊列的是由他們指定的具體執行任務。來自不同任務源的任務會進入到不同的任務隊列。其中setTimeout與setInterval是同源的。
分析示例代碼千言萬語,不如就著例子講來的清楚。下面我們可以按照規范,一步步執行解析下上面的例子,先貼一下例子代碼(免得你往上翻)。
console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); Promise.resolve().then(function() { console.log("promise1"); }).then(function() { console.log("promise2"); }); console.log("script end");
整體script作為第一個宏任務進入主線程,遇到console.log,輸出script start
遇到setTimeout,其回調函數被分發到宏任務Event Queue中
遇到Promise,其then函數被分到到微任務Event Queue中,記為then1,之后又遇到了then函數,將其分到微任務Event Queue中,記為then2
遇到console.log,輸出script end
至此,Event Queue中存在三個任務,如下表:
宏任務 | 微任務 |
---|---|
setTimeout | then1 then2 |
執行微任務,首先執行then1,輸出promise1,然后執行then2,輸出promise2,這樣就清空了所有微任務
執行setTimeout任務,輸出setTimeout
至此,輸出的順序是:script start, script end, promise1, promise2, setTimeout
so,你猜對了嗎?
看看你掌握了沒再來一個題目,來做個練習:
console.log("script start"); setTimeout(function() { console.log("timeout1"); }, 10); new Promise(resolve => { console.log("promise1"); resolve(); setTimeout(() => console.log("timeout2"), 10); }).then(function() { console.log("then1") }) console.log("script end");
這個題目就稍微有點復雜了,我們再分析下:
首先,事件循環從宏任務(macrotask)隊列開始,最初始,宏任務隊列中,只有一個script(整體代碼)任務;當遇到任務源(task source)時,則會先分發任務到對應的任務隊列中去。所以,就和上面例子類似,首先遇到了console.log,輸出script start;
接著往下走,遇到setTimeout任務源,將其分發到任務隊列中去,記為timeout1;
接著遇到promise,new promise中的代碼立即執行,輸出promise1,然后執行resolve,遇到setTimeout,將其分發到任務隊列中去,記為timemout2,將其then分發到微任務隊列中去,記為then1;
接著遇到console.log代碼,直接輸出script end
接著檢查微任務隊列,發現有個then1微任務,執行,輸出then1
再檢查微任務隊列,發現已經清空,則開始檢查宏任務隊列,執行timeout1,輸出timeout1;
接著執行timeout2,輸出timeout2
至此,所有的都隊列都已清空,執行完畢。其輸出的順序依次是:script start, promise1, script end, then1, timeout1, timeout2
用流程圖看更清晰:
總結有個小tip:從規范來看,microtask優先于task執行,所以如果有需要優先執行的邏輯,放入microtask隊列會比task更早的被執行。
最后的最后,記住,JavaScript是一門單線程語言。
參考文獻這一次,徹底弄懂 JavaScript 執行機制
Tasks, microtasks, queues and schedules
從一道題淺說 JavaScript 的事件循環
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93418.html
摘要:決定了注冊的事件是捕獲事件還是冒泡事件。瀏覽器會自動進行通信,實現通信的關鍵是后端。該方式只能用于二級域名相同的情況下,比如和適用于該方式。中的中的和瀏覽器中的不相同。 事件機制 事件觸發三階段 事件觸發有三個階段 window 往事件觸發處傳播,遇到注冊的捕獲事件會觸發 傳播到事件觸發處時觸發注冊的事件 從事件觸發處往 window 傳播,遇到注冊的冒泡事件會觸發 事件觸發一般...
摘要:事件循環了解了在引擎中是如何工作了之后,來看下如何使用異步回調函數來避免代碼。從回調函數被放入后秒鐘,把移到中。由于事件循環持續地監測調用棧是否已空,此時它一注意到調用棧空了,就調用并創建一個新的調用棧。 聽多了JavaScript單線程,異步,V8,便會很想去知道JavaScript是如何利用單線程來實現所謂的異步的。我參考了一些文章,了解到一個很重要的詞匯:事件循環(Event L...
摘要:的單線程,與它的用途有關。只要指定過回調函數,這些事件發生時就會進入任務隊列,等待主線程讀取。四主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。令人困惑的是,文檔中稱,指定的回調函數,總是排在前面。 原文:http://www.cnblogs.com/Master... 一、為什么JavaScript是單線程? JavaScript語言的一大特點...
摘要:由于個人精力有限,一些技術點的歸納可能有失偏頗,或者目前并未納入進來,因此上的清單內容也會不斷更新。 2018 眼看就要過去了,今年的你相較去年技術上有怎樣的收獲呢? 記得年初的時候我給自己制定了一個學習計劃,現在回顧來看完成度還不錯。但仍有些遺憾,一些技術點沒有時間去好好學習。 在學習中我發現,像文章這樣的知識往往是碎片化的,而前端涉及到的面很多,如果不將這些知識有效梳理,則無法形成...
閱讀 702·2021-09-29 09:34
閱讀 2554·2019-08-30 15:53
閱讀 3361·2019-08-29 17:17
閱讀 761·2019-08-29 16:08
閱讀 1120·2019-08-29 13:03
閱讀 951·2019-08-27 10:54
閱讀 688·2019-08-26 13:39
閱讀 2859·2019-08-26 13:34