ECMAScript規範沒有提到事件循環。相反,事件循環在HTML規範中有詳細說明,儘管事件循環是在HTML規範中定義的,可是在其餘環境中,好比Nodejs,也使用了它。javascript
爲了協調事件,用戶交互,腳本,渲染,網絡等,用戶代理必須使用本節所述的event loop。 Events,Parsing,Callbacks,Using a resource,Reacting to DOM manipulation
事件,用戶交互,腳本,渲染,網絡這些都是咱們所熟悉的東西,他們都是由event loop協調的。觸發一個click事件,進行一次ajax請求,背後都有event loop在運做。html
An event loop has one or more task queues. A task queue is a set of tasks.vue
一個 event loop有一個或多個任務隊列,一個任務隊列是一個多任務的集合。java
Task queues are sets, not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.node
任務隊列是集合,而不是隊列,由於事件循環處理模型的第一步從選擇的隊列中獲取第一個可運行的任務,而不是退出第一個任務。web
The microtask queue is not a task queue.ajax
微任務隊列不是任務隊列。segmentfault
在規範的Processing model定義了event loop的循環過程:api
1.在tasks隊列中選擇最老的一個task,用戶代理能夠選擇任何task隊列,
若是沒有可選的任務,則跳到下邊的microtasks步驟。
2.設置oldestTask成爲任務隊列中第一個可運行的任務,並將其從任務隊列中刪除。
5.運行oldestTask裏面的一系列腳本
7.從其任務隊列中刪除oldestTask
8.Microtasks: 執行microtasks任務檢查點。(也就是執行microtasks隊列裏的任務)
11.更新渲染(Update the rendering)...
複製代碼
所作的就是執行microtask隊列裏的任務。何時會調用microtask checkpoint呢?promise
<div class="outer">
<div class="inner"></div>
</div>
<script>
// Let's get hold of those elements var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); // Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// Here's a click listener… function onClick() { console.log('click'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise'); }); outer.setAttribute('data-random', Math.random()); } // …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
</script>
複製代碼
點擊打印結果
click
promise
mutate
click
promise
mutate
timeout
timeout
複製代碼
此代碼出處和解釋> jakearchibald.com/2015/tasks-…
事件循環容許Node.js執行非阻塞的I/O操做(儘管JavaScript是單線程的),方法是儘量地將操做卸載到系統內核。
下圖簡要的概述了event loop的操做順序:
注:每個框表明event loop中的一個階段
每一個階段都有一個FIFO(先進先出)的回調隊列等待執行。雖然每一個階段都有其獨特之處,但整體而言,當event loop進入到指定階段後,它會執行該階段的任何操做,並執行對應的回調直到隊列中沒有可執行回調或者達到回調執行上限,然後event loop會進入下一階段。
因爲任何這些階段的操做可能產生更多操做,內核也會將新的事件推入到poll階段的隊列中,因此新的poll事件被容許在處理poll事件時繼續加入隊,這也意味着長時間運行的回調能夠容許poll階段運行的時間比計時器的閾值要長
Between each run of the event loop, Node.js checks if it is waiting for any asynchronous I/O or timers and shuts down cleanly if there are not any.
在每次運行事件循環之間,Node.js檢查它是否在等待任何異步I/O或計時器,若是沒有,則乾淨地關閉。
兩者的調用順序取決於它們的執行上下文。若是二者都在主模塊被調用,那麼其回調被執行的時間點就取決於處理過程的性能(這可能被運行在同一臺機器上的其餘應用影響)
好比說,若是下列腳本不是在I/O循環中運行,這兩種定時器運行的順序是不必定的
// timeout_vs_immediate.js
setTimeout(function timeout() {
console.log('timeout');
}, 0);
setImmediate(function immediate() {
console.log('immediate');
});
複製代碼
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
複製代碼
可是若是你把上面的代碼置於I/O循環中,setImmediate回調會被優先執行:
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
複製代碼
$ node timeout_vs_immediate.js
immediate
timeout
$ node timeout_vs_immediate.js
immediate
timeout
複製代碼
使用setImmediate()而不是setTimeout()的主要好處是:若是代碼是在I/O循環中調用,那麼setImmediate()老是優先於其餘定時器(不管有多少定時器存在)
When delay is larger than 2147483647 or less than 1, the delay will be set to 1.
setTimeout(()=> {
console.log(1);
},1);
setTimeout(()=> {
console.log(0);
},0);
# output 能夠看到1在前 0在後
1 2
0 4
複製代碼
在瀏覽器中,setTimeout()/setInterval() 的每調用一次定時器的最小間隔是4ms,這一般是因爲函數嵌套致使(嵌套層級達到必定深度),或者是因爲已經執行的setInterval的回調函數阻塞致使的。例如:
let i = 0
let now = Date.now()
function f(){
let newD = Date.now()
console.log(newD-now)
now = newD
}
function cb() {
f();
i++
if(i>10) return
setTimeout(cb, 0);
}
setTimeout(cb, 0)
複製代碼
你可能已經注意到process.nextTick()不在上面的圖表中,即便它也是異步api。這是由於嚴格意義上來講process.nextTick()不屬於event loop中的一部分,它會忽略event loop當前正在執行的階段,而直接處理nextTickQueue中的內容。
回過頭看一下圖表,你在任何給定階段調用process.nextTick(),在繼續event loop以前,全部傳入process.nextTick()的回調都會被執行。這可能會致使一些很差的狀況,由於它容許你遞歸調用process.nextTick()從而使得event loop沒法進入poll階段,致使沒法接收到新的 I/O事件
這裏有兩個主要的緣由
如下截圖來源:《深刻淺出node.js》