回調(diào)與異步編程
一、回調(diào)函數(shù)的使用場景
- 異步編程。
- 事件監(jiān)聽、處理。
- setTimeout、setInterval方法。
- 通用功能,簡化邏輯。
二、異步編程的4種方法
- 回調(diào)函數(shù)。
- 事件監(jiān)聽。
- 發(fā)布訂閱。
- Promise對象。
回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個參數(shù)傳遞給其他的代碼,其作用是在需要的時候方便調(diào)用這段代碼。
fn1(fn2)
function fn1 () {
// to do...
fn2()
}
function fn2 () {
// to do...
}
說到異步編程,那么就有必要了解js的事件循環(huán)機制Event Loop

1. 棧和隊列

棧: 后進先出(LIFO-last in first out):最后插入的元素最先出來。
隊列:先進先出(FIFO-first in first out):最先插入的元素最先出來。
2. 宏任務(wù)和微任務(wù)(瀏覽器)
宏隊列,macrotask。一些異步任務(wù)的回調(diào)會依次進入macro task queue,等待后續(xù)被調(diào)用:
setTimeout和setInterval
requestAnimationFrame
I/O
UI rendering
微隊列,microtask。 另一些異步任務(wù)的回調(diào)會依次進入micro task queue,等待后續(xù)被調(diào)用:
Promise.then
Object.observe
MutationObserver
執(zhí)行順序:
執(zhí)行全局Script同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句(比如setTimeout等),遇到異步代碼根據(jù)上述任務(wù)劃分到對應(yīng)隊列中;
全局Script代碼執(zhí)行完畢后,調(diào)用棧Stack會清空;
從微隊列microtask queue中取出位于隊首的回調(diào)任務(wù),放入調(diào)用棧Stack中執(zhí)行,執(zhí)行完后microtask queue長度減1;
繼續(xù)取出位于隊首的任務(wù),放入調(diào)用棧Stack中執(zhí)行,以此類推,直到直到把microtask queue中的所有任務(wù)都執(zhí)行完畢。(即清空微任務(wù)隊列,注意:如果在執(zhí)行microtask的過程中,又產(chǎn)生了microtask,那么會加入到隊列的末尾,也會在這個周期被調(diào)用執(zhí)行)
microtask queue中的所有任務(wù)都執(zhí)行完畢,此時microtask queue為空隊列,調(diào)用棧Stack也為空;
取出宏隊列macrotask queue中位于隊首的任務(wù),放入Stack中執(zhí)行;
執(zhí)行完畢后,調(diào)用棧Stack為空;
重復(fù)第3-7個步驟;
...
簡單來說,執(zhí)行主線程同步代碼遇到異步任務(wù)掛起劃分到對應(yīng)任務(wù)隊列,主線程同步代碼執(zhí)行完畢后,清空微任務(wù)隊列,此時微任務(wù)清空,執(zhí)行棧清空,開始執(zhí)行宏任務(wù),執(zhí)行完一個宏任務(wù)后查看微任務(wù)隊列并執(zhí)行清空,之后繼續(xù)執(zhí)行宏任務(wù)依次循環(huán)。
概念性的東西就這么多,來看幾個示例代碼,測試一下你是否掌握了:
console.log(1)
setTimeout(() => {
console.log(2)
Promise.resolve().then(() => {
console.log(3)
})
})
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then(data => {
console.log(data)
return 6
}).then(data => {
console.log(data)
})
setTimeout(() => {
console.log(7)
Promise.resolve().then(() => {
console.log(8)
})
})
console.log(9)
1 4 9 5 6 2 3 7 8