首先,看一道面試題
//請(qǐng)寫出輸出內(nèi)容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
這道題目需要我們了解js的運(yùn)行機(jī)制
單線程
js的一大特點(diǎn)就是單線程讲婚,也就是說同一時(shí)間只能做一件事情。
js單線程的原因就是它是一個(gè)腳本語言够掠,主要用于用戶交互以及操作DOM洗显。如果是多線程搔耕,那么如果一個(gè)線程刪除了DOM阵幸,另一個(gè)線程又在操作這個(gè)DOM臊诊,那么應(yīng)該以哪個(gè)為準(zhǔn)呢癞埠?所以他的用途決定了它只能是單線程
任務(wù)隊(duì)列
單線程就表示所有的任務(wù)都要排隊(duì)状原,只有前面的任務(wù)執(zhí)行完畢之后才能執(zhí)行下一個(gè)任務(wù)聋呢。
有的時(shí)候因?yàn)閍jax請(qǐng)求等任務(wù)需要等待的時(shí)間很長,如果要等到請(qǐng)求完成在執(zhí)行下一個(gè)任務(wù)颠区,會(huì)浪費(fèi)大量時(shí)間削锰。此時(shí)js的設(shè)計(jì)者就想到了,可以將這些任務(wù)掛起毕莱,繼續(xù)執(zhí)行下面的任務(wù)器贩,等到請(qǐng)求有了結(jié)果再去執(zhí)行掛起的任務(wù)。
于是所有的任務(wù)可以分成同步任務(wù)和異步任務(wù)朋截。同步任務(wù)指的是在主線程上執(zhí)行蛹稍,只有當(dāng)前任務(wù)執(zhí)行完畢才能執(zhí)行下一個(gè)任務(wù)。異步任務(wù)是指不進(jìn)入主線程部服,而進(jìn)入"任務(wù)隊(duì)列"的任務(wù)唆姐。當(dāng)異步任務(wù)有了運(yùn)行結(jié)果,在可運(yùn)行的異步任務(wù)添加到執(zhí)行棧中
所以我們要明白以下幾點(diǎn):
- js分為同步任務(wù)和異步任務(wù)
- 同步任務(wù)都在主線程中形成廓八,形成一個(gè)執(zhí)行棧
- 異步任務(wù)會(huì)存放在任務(wù)隊(duì)列中奉芦,當(dāng)異步任務(wù)有了運(yùn)行結(jié)果,就放在任務(wù)隊(duì)列中放置一個(gè)事件
- 當(dāng)執(zhí)行棧中所有的同步任務(wù)執(zhí)行完畢之后剧蹂,就會(huì)去執(zhí)行任務(wù)隊(duì)列中放置的任務(wù)
- 以上過程會(huì)重復(fù)執(zhí)行
宏任務(wù)
(macro)task也就是宏任務(wù):我們可以認(rèn)為每次在執(zhí)行棧中執(zhí)行的代碼就是一個(gè)宏任務(wù)
(macro)task包括:script(整體代碼)声功、setTimeOut、setInterval宠叼、I/O减噪、UI交互事件、postMessage车吹、MessageChannel、setImmediate(Node.js環(huán)境)
微任務(wù)
microtask也就是微任務(wù):當(dāng)前task結(jié)束之后立即執(zhí)行的任務(wù)醋闭。這些任務(wù)響應(yīng)速度比setTimeout會(huì)更快窄驹,因?yàn)闊o需等待渲染。
microtask主要包括:Promise.then证逻、MutaionObserver乐埠、process.nextTick(Node.js環(huán)境)
事件循環(huán)
主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過程是循環(huán)不斷的囚企,所以整個(gè)的這個(gè)運(yùn)行機(jī)制叫事件循環(huán)
在事件循環(huán)中丈咐,每一次循環(huán)成為一個(gè)tick,每次tick任務(wù)處理模型比較復(fù)雜龙宏,但是關(guān)鍵步驟如下:
- 執(zhí)行一個(gè)宏任務(wù)(棧中沒有就從事件隊(duì)列中獲取)
- 執(zhí)行中遇到了微任務(wù)棵逊,就放到微任務(wù)隊(duì)列中
- 宏任務(wù)執(zhí)行完畢之后,去微任務(wù)隊(duì)列中依次執(zhí)行所有的微任務(wù)
- 當(dāng)前宏任務(wù)執(zhí)行完畢之后银酗,開始檢查渲染辆影,然后GUI線程接管渲染
- 渲染完畢之后徒像,js線程繼續(xù)接管,開始下一個(gè)宏任務(wù)
Promise和async
Promise和async的異步體現(xiàn)在then和catch中蛙讥,所以寫在Promise和async中的代碼會(huì)立即執(zhí)行
async本身就是promise+generator的語法糖锯蛀。所以await后面的代碼就是microtask
async function async1() {
console.log('async1 start');
await async2();
}
等價(jià)于
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}
代碼分析
1.首先將整個(gè)代碼(script)壓如執(zhí)行棧
2.然后定義了兩個(gè)async方法,接著是一個(gè)console次慢,將console壓棧旁涤,然后輸出script start
3.接著遇到setTimeout,這是一個(gè)宏任務(wù)
4.接著執(zhí)行async1迫像,async1中的第一個(gè)console直接入棧劈愚,輸出async1 start,然后執(zhí)行await后面的async2侵蒙,輸出async2造虎,await之后的代碼就是一個(gè)微任務(wù)
5.下面遇到promise,promise的異步體現(xiàn)在then中纷闺,所以promise中的代碼立即壓棧算凿,執(zhí)行console,輸出promise1犁功,將then中的代碼放入到微任務(wù)中氓轰,然后執(zhí)行最后一句console,輸出script end
6.這時(shí)候第一宏任務(wù)已經(jīng)全部執(zhí)行完畢浸卦,此時(shí)我們執(zhí)行微任務(wù)隊(duì)列中的兩個(gè)console署鸡,依次輸出async1 end 和promise2
7.然后執(zhí)行下一個(gè)宏任務(wù),也就是setTimeout,輸出setTimeout
參考文獻(xiàn):
1.https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
2.http://www.ruanyifeng.com/blog/2014/10/event-loop.html