衆所周知,javascript是一門單線程語言,而當咱們使用ajax和服務端進行通訊的時候是須要必定時間的,這樣當前線程就會被阻塞,使瀏覽器失去相應。所以,當js執行執行一些長時間的任務時,咱們但願有一種異步的方式處理這種任務。事件循環(event loop)就是如何處理異步執行順序的一種機制。javascript
$.get(url, function (data) {
//do something
});
複製代碼
接下來會一一介紹,事件循環中的執行棧
、事件隊列
、宏任務
、微任務
等概念java
執行棧就是js代碼運行的地方,上圖call stack所示。當下面程序運行時,會推送的調用棧中被執行。node
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 500);
console.log('Bye');
複製代碼
當瀏覽器中的事件監聽函數被觸發(DOM)、網絡請求的相應(ajax)、定時器被觸發(setTimeout)相對應的回調函數就會被推送到事件隊列中,等待執行;如上圖中的Callback Queue。ajax
事件循環是一個這樣的過程:當執行棧中的任務結束以後,會將事件隊列中的第一個任務推入到執行棧中執行,當任務處理完畢,又會取事件隊列中的第一個任務,如此往復,便構成了事件循環。promise
對應到下面代碼中。瀏覽器
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 500);
console.log('Bye');
複製代碼
經過上面的例子會對執行棧和事件隊列有個基本的認識。因爲JS是單線程的,同步任務會形成瀏覽器阻塞,咱們把任務分紅一個一個的異步任務,經過事件循環來執行事件隊列中的任務。這就使得當咱們掛起某一個任務的時候能夠去作一些其餘的事情,而不須要等待這個任務執行完畢。因此事件循環的運行機制大體分爲如下步驟:bash
一、檢查事件隊列是否爲空,若是爲空,則繼續檢查;如不爲空,則執行 2;網絡
二、取出事件隊列的首部,壓入執行棧;dom
三、執行任務;異步
四、檢查執行棧,若是執行棧爲空,則跳回第 1 步;如不爲空,則繼續檢查;
咱們知道DOM操做會觸發瀏覽器渲染,如增、刪節點,改變背景顏色。那麼這類操做是如何在瀏覽器當中奏效的?
至此咱們已經知道了事件循環是如何執行的,事件循環器會不停的檢查事件隊列,若是不爲空,則取出隊首壓入執行棧執行。當一個任務執行完畢以後,事件循環器又會繼續不停的檢查事件隊列,不過在這間,瀏覽器會對頁面進行渲染。這就保證了用戶在瀏覽頁面的時候不會出現頁面阻塞的狀況,這也使 JS 動畫成爲可能。
function move() {
setTimeout(() => {
dom.style.left = dom.offsetLeft + 10 + 'px'
move()
}, 15);
}
move()
複製代碼
如今用事件循環的機制說明js動畫的過程。上面代碼會在執行棧中執行,move函數被調用,setTimeout的回調函數15ms以後會被推送到事件隊列中。此時執行棧中的任務結束,瀏覽器渲染、檢查事件隊列不斷循環。當15ms以後事件隊列中有任務時,會被推送到執行棧中執行,這時dom節點向右偏移10px,move函數執行、執行棧結束,瀏覽渲染、檢查事件隊列。如此往復就造成了動畫。
先看一段代碼,是如何輸出的;
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 start'
、'script end'
、'promise1'
、'promise2'
、'setTimeout'
。
setTimeout的回調函數是宏任務、Promise的回調函數是微任務。微任務和宏任務同樣遵循事件循環機制,可是他們仍是有些差異。
一、宏任務和微任務的事件隊列是相互獨立的;
二、微任務隊列的檢查時機早於宏任務。(執行棧中任務結束就會立刻清空微任務事件隊列)
根據上面的規則,解釋代碼的輸出。
執行棧中的代碼執行,宏任務推入宏任務事件隊列、微任務推入微任務事件隊列,執行棧任務結束
檢查微任務事件隊列,此時已經有Promise的回調函數,推入執行棧,輸出promise1
。Promise還有回調函數,推入微任務事件隊列,執行棧結束。
檢查微任務事件隊列,推入執行棧,輸出promise2
,執行棧結束。
檢查微任務事件隊列,此時被清空
檢查宏任務事件隊列,推入執行棧,輸出setTimeout
,執行棧結束。
宏任務有: **setTimeout** 、**setImmediate** 、 **MessageChannel**
微任務有: **setTimeout** 、**setImmediate** 、 **MessageChannel**
複製代碼
Node中的事件循環是和瀏覽器有很大區別的
當Node.js啓動時,會初始化event loop;每一個event loop都會包含按以下順序六個循環階段
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────
複製代碼
每個階段都有一個裝有callbacks的fifo queue(隊列),當event loop運行到一個指定階段時, node將執行該階段的fifo queue(隊列),當隊列callback執行完或者執行callbacks數量超過該階段的上限時,event loop會轉入下一下階段。
宏任務:setTimeout和setImmediate
複製代碼
誰先輸出,誰後輸出?
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
複製代碼
答案是不肯定的。有兩個前提咱們是須要清楚的;
當:event loop準備時間 > setTimeout最小毫秒數。從timers階段檢查,此時隊列中已經有setTimeout的任務,因此timeout
先輸出;
當:event loop準備時間 < setTimeout最小毫秒數。從timers階段檢查,此時隊列是空的就下檢查接下來的階段,到check階段,已經有setImmediate的任務,因此immediate
先輸出;
微任務:process.nextTick()和Promise.then()
複製代碼
微任務不在event loop的任何階段執行,而是在各個階段切換的中間執行,即從一個階段切換到下個階段前執行;nextTick比Promise.then()先執行
下面代碼是如何執行的。
setImmediate(() => {
console.log('setImmediate1')
setTimeout(() => {
console.log('setTimeout1')
}, 0);
})
setTimeout(()=>{
process.nextTick(()=>console.log('nextTick'))
console.log('setTimeout2')
setImmediate(()=>{
console.log('setImmediate2')
})
},0);
複製代碼
setImmediate1
,setTimeout的任務添加到timer階段setTimeout2
,setImmediate的任務添加到check階段。setTimeout1
nextTick
setImmediate2
let fs = require('fs')
fs.readFile('./1.txt', 'utf8', function (err, data) {
setTimeout(() => {
console.log('setTimeout')
}, 0);
setImmediate(() => {
console.log('setImmediate')
})
})
複製代碼
這種狀況下的setTimeout和setImmediate執行的順序肯定嗎?readFile的回調函數是在poll階段執行 答案是setImmediate
比setTimeout
先執行
瀏覽器中和Node.js中的事件循環能夠說是兩套不一樣的機制,作個總結,但願有所幫助。