前言
學習
JavaScript執行機制能更好的理解JavaScript的代碼執行順序,進而更好的理解JavaScript的異步模式,
Event Loop即事件回圈,是瀏覽器或Node解決單執行緒運行時不會阻塞的一種機制,(也可以理解為經常使用的異步)
在正式學習Event Loop之前,先提出幾個問題:
- 什么是同步與異步?
JavaScript是一門單執行緒語言,那如何實作異步?- 同步任務和異步任務的執行順序如何?
- 異步任務是否存在優先級?
同步與異步
計算機領域中的同步與異步和我們現實社會的同步和異步正好相反,現實中的同步,就是同時進行,突出的是"同",比如看足球比賽的時候吃著零食,兩件事情同時發生;異步就是不同時,但計算機中與之存在一定差異,
舉個例子:
早上剛醒來想喝點熱水暖暖身子,因此就要去燒水,由于早上的時間很寶貴,不能空等水開,這時候一般會去洗漱,打扮自己,
洗漱完,水開了,喝到熱水,暖身成功,打工人出發!
燒水和洗漱是在同時間進行的,這就是計算機中的異步,計算機中的同步是連續性的動作,上一步未完成前,下一步會發生堵塞,直至上一步完成后,下一步才可以執行,例如:只有等水開,才能喝到暖暖的熱水,
單執行緒卻可以異步?
JavaScript的確是一門單執行緒語言,但是瀏覽器UI是多執行緒的,異步任務借助瀏覽器的執行緒和JavaScript的執行機制實作,
例如,setTimeout就借助瀏覽器定時器觸發執行緒的計時功能來實作,
瀏覽器執行緒
GUI渲染執行緒- 繪制頁面,決議HTML、CSS,構建DOM樹等
- 頁面的重繪和重排
- 與JS引擎互斥(JS引擎阻塞頁面重繪)
JS引擎執行緒- js腳本代碼執行
- 負責執行準備好的事件,例如定時器計時結束或異步請求成功且正確回傳
- 與GUI渲染執行緒互斥
- 事件觸發執行緒
- 當對應的事件滿足觸發條件,將事件添加到js的任務佇列末尾
- 多個事件加入任務佇列需要排隊等待
- 定時器觸發執行緒
- 負責執行異步的定時器類事件:setTimeout、setInterval等
- 瀏覽器定時計時由該執行緒完成,計時完畢后將事件添加至任務佇列隊尾
HTTP請求執行緒- 負責異步請求
- 當監聽到異步請求狀態變更時,如果存在回呼函式,該執行緒會將回呼函式加入到任務佇列隊尾
關于JavaScript的執行順序,就要進入本文的核心:Event Loop,
同步與異步執行順序
JavaScript將任務分為同步任務和異步任務,同步任務進入主線中中,異步任務首先到Event Table進行回呼函式注冊,- 當異步任務的觸發條件滿足,將回呼函式從
Event Table壓入Event Queue中, - 主執行緒里面的同步任務執行完畢,系統會去
Event Queue中讀取異步的回呼函式, - 只要主執行緒空了,就會去
Event Queue讀取回呼函式,這個程序被稱為Event Loop,
- 觸發條件滿足: 舉個栗子,setTimeout(cb, 1000),當1000ms后,就講cb壓入Event Queue,
- 再舉個栗子,ajax(請求條件, cb),當http請求發送成功后,cb壓入Event Queue,
Event Loop執行的流程如下:

下面一起來看一個例子,熟悉一下上述流程,
// 下面代碼的列印結果?
// 同步任務 列印 first
console.log("first");
setTimeout(() => {
// 異步任務 壓入Event Table 4ms之后 cb壓入Event Queue
console.log("second");
},0)
// 同步任務 列印last
console.log("last");
// 讀取Event Queue 列印second
有哪些異步任務會進入 Event Queue呢?
DOM事件AJAX請求- 定時器
setTimeout和setlnterval ES6的Promise
異步任務的優先級
下面繼續來看一個案例:
setTimeout(() => {
console.log(1);
}, 1000)
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log(3)
});
console.log(4)
按照上面的學習:
可以很輕松得出案例的列印結果:2,4,1,3,
Promise定義部分為同步任務,回呼部分為異步任務
將案例代碼在控制臺運行,最侄訓傳結果卻有些出人意料:

剛看到如此結果,我的第一感覺是,setTimeout函式1s觸發太慢導致它加入Event Queue的順序晚于Promise.then
于是我修改了setTimeout的回呼時間為0(瀏覽器最小觸發時間為4ms),但結果仍為發生改變,
那么也就意味著,JavaScript的異步任務是存在優先級的,
宏任務和微任務
JavaScript除了廣義上將任務劃分為同步任務和異步任務,還對異步任務進行了更精細的劃分,

history traversal任務(h5當中的歷史操作)process.nextTick(nodejs中的一個異步操作)MutationObserver(h5里面增加的,用來監聽DOM節點變化的)
不同的任務會進入對應的Event Queue,
更新一下Event Loop的執行順序圖:

Event Loop執行程序
- 代碼開始執行,創建一個全域呼叫堆疊,script作為宏任務執行
- 執行程序過同步任務立即執行,異步任務根據異步任務型別分別注冊到微任務佇列和宏任務佇列
- 同步任務執行完畢,查看微任務佇列
- 若存在微任務,將微任務佇列全部執行(包括執行微任務程序中產生的新微任務)
- 若無微任務,查看宏任務佇列,執行第一個宏任務,宏任務執行完畢,查看微任務佇列,重復上述操作,直至宏任務佇列為空
總結
在上面學習的基礎上,重新分析當前案例:
setTimeout(() => {
console.log(1);
}, 1000)
new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log(3)
});
console.log(4)
分析程序見下圖:

附贈
文章的最后附贈經典爛大街面試題,可以測驗一下自己的掌握程度,
console.log('script start');
setTimeout(() => {
console.log('time1');
}, 1 * 2000);
Promise.resolve()
.then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
async function foo() {
await bar()
console.log('async1 end')
}
foo()
async function errorFunc () {
try {
await Promise.reject('error!!!')
} catch(e) {
console.log(e)
}
console.log('async1');
return Promise.resolve('async1 success')
}
errorFunc().then(res => console.log(res))
function bar() {
console.log('async2 end')
}
console.log('script end');
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/305707.html
標籤:其他
