瀏覽器(或者說JS引擎)執(zhí)行JS的機制是基于事件循環(huán)。
由于JS是單線程神得,所以同一時間能且只能執(zhí)行一個任務频轿,其他任務就得排隊,后續(xù)任務必須等到前一個任務結束才能開始執(zhí)行痊银。
為了避免因為某些長時間任務造成的無意義等待抵蚊,JS引入了異步的概念,用另一個線程來管理異步任務溯革。
同步任務直接在主線程隊列中順序執(zhí)行贞绳,而異步任務會進入另一個任務隊列,不會阻塞主線程致稀。等到主線程隊列空了(執(zhí)行完了)的時候冈闭,就會去異步隊列查詢是否有可執(zhí)行的異步任務了(異步任務通常進入異步隊列之后還要等一些條件才能執(zhí)行,如ajax請求抖单、文件讀寫)萎攒,如果某個異步任務可以執(zhí)行了便加入主線程隊列,以此循環(huán)臭猜。
先來看一下實現(xiàn)異步的一些方式躺酒,下面用分類的方式列舉一下
- 經(jīng)典的回調(diào)函數(shù)
- callback
- 監(jiān)聽事件
- onload
- new Image
function asynByImg( callback ) {
var img = new Image();
img.onload = img.onerror = img.onreadystatechange = function() {
img = img.onload = img.onerror = img.onreadystatechange = null;
callback();
}
img.src = "data:image/png,";
}
asynByImg(function(){
console.log(1);
});
console.log(2);
- 事件綁定
- 發(fā)布/訂閱模式
- Message
- 延遲類
- setTimeout
setTimeout(function() {
console.log(1);
});
console.log(2);
- setInterval
setInterval(function() {
console.log(1);
});
console.log(2);
- requestAnimationFrame
(function() {
var requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var startTime = window.mozAnimationStartTime || Date.now();
requestAnimationFrame(draw);
function draw (timestamp) {
// 計算兩次重繪的時間間隔
var drawStart = timestamp || Date.now();
var diff = drawStart - startTime;
startTime = drawStart;
requestAnimationFrame(draw);
}
})()
- setImmediate
- process.nextTick()
- 異步功能函數(shù)
- promise
- ajax
- async / await
- Worker
- Co / Generator
這些方式的執(zhí)行順序是什么樣的?為什么蔑歌?
先看下面的示例:
console.log('start')
const interval = setInterval(() => {
console.log('setInterval')
}, 0)
setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 5')
})
.then(() => {
console.log('promise 6')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)
Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})
上面的執(zhí)行結果依次打印下面的內(nèi)容
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
setInterval
羹应。。次屠。
想要知道上面的原理需要先來了解下面的幾個內(nèi)容
- 事件循環(huán)
JavaScript在一個時間僅處理一個任務. 就是JavaScript在執(zhí)行時, 存在一個執(zhí)行隊列, 依次執(zhí)行隊列中的任務, 不能同時執(zhí)行多個任務园匹。主線程從"任務隊列"中讀取事件,這個過程是循環(huán)不斷的劫灶,所以整個的這種運行機制又稱為Event Loop(事件循環(huán))關于事件循環(huán)詳細說明可以查看這里裸违。
這個過程用一個現(xiàn)實的例子就像是去銀行只有一個窗口,辦理業(yè)務就要排隊本昏,好痛苦供汛。但是如果一個任務需要執(zhí)行很久很久,腫么辦涌穆?等著唄怔昨。但是聰明的程序員怎么會忍受一個任務執(zhí)行那么長時間,于是把任務分成了macrotasks和microtasks兩類宿稀。 - macrotasks和microtasks
macrotasks【同步任務】: script(整體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks【異步任務】: process.nextTick, Promises, Object.observe, MutationObserver
同步任務以及異步任務會進入不同的“場所”趁舀,同步的進入主線程,異步的進入Event Table并注冊函數(shù)祝沸。當指定的事情完成時矮烹,Event Table會將這個函數(shù)移入Event Queue越庇。主線程執(zhí)行完畢為空,就會去Event Queue讀取對應的函數(shù)奉狈,進入主線程執(zhí)行卤唉。
然后解析上面代碼的執(zhí)行:
1. 同步任務直接放入到主模塊(主線程)任務隊列執(zhí)行. 異步任務掛起后臺執(zhí)行, 等待IO事件完成或行為事件被觸發(fā).
2. 系統(tǒng)后臺執(zhí)行異步任務, 如果某個異步任務事件發(fā)生(或者是行為事件被觸發(fā)), 則將該任務push到任務隊列中, 每個任務會對應一個回調(diào)函數(shù)進行處理. 這個步驟在后臺一直執(zhí)行, 因為就不斷有事件被觸發(fā), IO不斷完成, 任務被不斷的加入到任務隊列中.
3. 執(zhí)行任務隊列中的任務. 任務的具體執(zhí)行是在執(zhí)行棧中完成的. 當運行棧中一個任務的基本運行單元(稱之為Frame, 楨)全部執(zhí)行完畢后, 去讀取任務隊列中的下一個任務, 繼續(xù)執(zhí)行. 是一個循環(huán)的過程. 處理一個任務隊列中的任務, 稱之為一個tick.
即 macrotasks執(zhí)行console.log('start')
執(zhí)行后打印了‘start’; 然后macrotasks完成進入microtasks,執(zhí)行了console.log('promise 1')
和console.log('promise 2')
;microtasks執(zhí)行完成返回查看macrotasks執(zhí)行了console.log('setInterval')
和console.log('setTimeout 1')
; microtasks執(zhí)行console.log('promise 3')
和console.log('promise 4')
; 再次進入macrotasksconsole.log('setInterval')
和console.log('setTimeout 2')
;最后執(zhí)行microtasks隊列console.log('promise 5')
和console.log('promise 6')
嘹吨,到這里microtasks全部完成搬味,后面執(zhí)行macrotasks
備注:事件隊列是遵循先進先出, 需要依次處理. 所以, 定時器函數(shù)運行時,如果遇到了定時的事件, 事件發(fā)生, 也僅僅是將該任務push入任務隊列而已(并沒有立即執(zhí)行回調(diào)函數(shù))蟀拷。同時這也是為什么setTimeout預定的時間不一定會執(zhí)行碰纬,例如你預定300ms后執(zhí)行,它最早是300ms的原因