這是我參與更文挑戰的第20天,活動詳情查看: 更文挑戰javascript
面試官:給我說說事件循環吧,就是EventLoop的機制,這個你據說過吧。html
我:...💥💥💥🚀java
先看這張圖,先無論宏任務,微任務是什麼,先看整個流程。web
分析:面試
判斷宏任務隊列是否爲空promise
判斷微任務隊列是否爲空瀏覽器
由於首次執行宏隊列中會有 script(總體代碼塊)任務,因此實際上就是 Js 解析完成後,在異步任務中,會先執行完全部的微任務,這裏也是不少
面試題喜歡考察的。
須要注意的是,新建立的微任務會當即進入微任務隊列排隊執行,不須要等待下一次輪迴。markdown
主線程從任務隊列讀取事件,這個過程是循環不斷的,因此整個運行機制又稱爲 Event Loop(事件循環)。
數據結構
在深刻事件循環機制以前,須要弄懂一下幾個概念:異步
執行上下文( Execution context )
執行棧( Execution stack )
微任務( micro-task )
宏任務( macro-task )
執行上下文( Execution context )
執行上下文是一個抽象的概念,能夠理解爲是代碼執行的一個環境。JS 的執行上下文分爲三種,
全局執 行上下文、函數(局部)執行上下文、Eval 執行上下文。
全局執行上下文
:全局執行上下文指的是全局 this 指向的 window ,能夠是外部加載的 JS 文件 或者本地 標籤中的代碼。函數執行上下文
:函數上下文也稱爲局部上下文,每一個函數被調用的時候,都會建立一個新的局部 上下文。執行棧( Execution stack )
執行棧,就是咱們數據結構中的「棧」,它具備「先進後出」的特色,正是由於這種特色,在咱們代碼進行 執行的時候,遇到一個執行上下文就將其依次壓入執行棧中。
當代碼執行的時候,先執行位於棧頂的執行上下文中的代碼,當棧頂的執行上下文代碼執行完畢就會出 棧,繼續執行下一個位於棧頂的執行上下文。
function foo() {
console.log('a');
bar();
console.log('b');
}
function bar() {
console.log('c');
}
foo();
複製代碼
咱們都知道 Js 是單線程都,可是一些高耗時操做就帶來了進程阻塞問題。爲了解決這個問題,Js 有兩種任務的執行模式:
同步模式(Synchronous)和異步模式(Asynchronous)
。
在異步模式下,建立異步任務主要分爲宏任務與微任務兩種。ES6 規範中,
宏任務(Macrotask) | 微任務(Microtask) |
---|---|
setTimeout | requestAnimationFrame(有爭議) |
setInterval | MutationObserver(瀏覽器環境) |
MessageChannel | Promise.[ then/catch/finally ] |
I/O,事件隊列 | process.nextTick(Node環境) |
setImmediate(Node環境) | queueMicrotask |
script(總體代碼塊) |
注意:nextTick 隊列會比 Promie 隊列先執行。
如何理解 script(總體代碼塊)是個宏任務呢 🤔
實際上若是同時存在兩個 script 代碼塊,會首先在執行第一個 script 代碼塊中的同步代碼,若是這個過程當中建立了微任務並進入了微任務隊列,第一個 script 同步代碼執行完以後,會首先去清空微任務隊列,再去開啓第二個 script 代碼塊的執行。
因此這裏應該就能夠理解 script(總體代碼塊)爲何會是宏任務。
以上概念弄明白以後,再來看循環機制是如何運行的呢?如下涉及到的任務執行順序都是靠函數調用棧 來實現的。
script
標籤內的代碼開始的,上邊咱們提到過,整個script
標籤 做爲一個宏任務處理的。setTimeout
,就會將當前任務分發到對應的執行隊 列中去。 Promise
,在建立 Promise
實例對象時,代碼順序執行,若是 到了執行· then
操做,該任務就會被分發到微任務隊列中去。script
標籤內的代碼執行完畢,同時執行過程當中所涉及到的宏任務也和微任務也分配到相應的隊 列中去。setTimeout
任務隊列除此以外,還能夠放定時器的回調函數,須要指定某些代碼多少時間以後執行。
定時器主要包括兩種, setTimeout 和 setInterval 兩個函數
。 當咱們設置定時器的時間,執行某個特定的任務,以下:
// 1 秒後執行
setTimeout(function () {
console.log(2);
}, 1000);
console.log(1)
複製代碼
上述的輸出結果爲 1, 2,執行完同步代碼後,就會執行定時器中的任務事件
// 同步執行完當即執行
setTimeout(function () {
console.log(2);
}, 0);
console.log(1)
複製代碼
當咱們執行 setTimeout(fn,0)
定時器時,會將這個定時任務回調放在任務隊列的尾部,表明的含 義就是儘早的執行。
也就是等到主線程同步任務和"任務隊列"現有的事件都處理完,而後纔會當即執行這個定時器的任務。
上述的前提是,等到同步任務和任務隊列的代碼執行完畢後,若是當前代碼執行很長時間,定時器並沒 辦法保證必定在指定時間執行。
注:HTML5 標準規定了setTimeout() 的第二個參數的最小值(最短間隔),不得低於4毫秒, 若是低於這個值,就會自動增長。
若是涉及到頁面的改動,這個定時器任務一般不會當即執行,而是 16 毫秒執行一次,咱們一般使用 requestAnimationFrame() 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>消息運行機制</title>
</head>
<body>
</body>
<script> console.log('1'); setTimeout(() => { console.log('2') }, 1000); new Promise((resolve, reject) => { console.log('3'); resolve(); console.log('4'); }).then(() => { console.log('5'); }); console.log('6');// 1,3,4,6,5,2 </script>
</html>
複製代碼
分析:
<script>
標籤內的同步代碼,此時全局的代碼進入執行棧中,同步順序執行代碼,輸出 1。setTimeout (宏任務)
,將其分配到宏任務異步隊列中。 promise 異步代碼(微任務)
。可是構造函數中的代碼爲同步代碼,依次輸出三、4,則then
以後的任務加入到微任務隊列中去。script
內的代碼做爲宏任務處理,因此這次循環進行處處理微任務隊列中的全部異步任務,直達微任務隊列中的全部任務執行完成爲止,微任務隊列中只有一個微任務,因此輸出 5。 setTimeout
,最後輸出 2。以上不免有些囉嗦,因此簡化整理以下步驟: