javascript異步詳解1:事件循環(huán)機制EventLoop

一. js運行機制

js是單線程,但是存在同步【阻塞】和異步【非阻塞】執(zhí)行模式

  • 同步:從上到下、從左到右的?式執(zhí)?代碼邏輯
  • 異步:和同步對?,所以異步模式的代碼是不會按照默認順序執(zhí)?的。在解釋時,如果遇到異步模式的代碼,引擎會將當前的任務“掛起”并略過

瀏覽器的線程組成

1. GUI渲染線程
2. JavaScript引擎線程
3. 事件觸發(fā)線程
4. 定時器觸發(fā)線程
5. http請求線程
6. 其他線程

在JavaScript代碼運?的過程中實際執(zhí)?程序時同時只存在?個活動線程,這?實現(xiàn)同步異步就是靠 多線程切換的形式來進?實現(xiàn)的。

所以我們通常分析時,將上?的細分線程歸納為下列兩條線程:

  1. 【主線程】:這個線程?了執(zhí)???的渲染,JavaScript代碼的運?,事件的觸發(fā)等等
  2. 【?作線程】:這個線程是在幕后?作的,?來處理異步任務的執(zhí)?來實現(xiàn)?阻塞的運?模式

二. 事件循環(huán)機制

  1. 宏任務:

    宏任務是JavaScript中最原始的異步任務,包括setTimeout、setInterVal、AJAX等,在代碼執(zhí)?環(huán)境中按照同步代碼的順序,逐個進??作線程掛起,再按照異步任務到達的時間節(jié)點,逐個進?異步任務隊列,最終按照隊列中的順序進?函數(shù)執(zhí)?棧進?執(zhí)?。

    1 `script`
    2 setTimeout
    3 setInterval
    4 setImmediate(node)
    5 I/O 網(wǎng)絡請求完成、文件讀寫完成事件
    6 UI rendering
    7 requestAnimationFrame(瀏覽器)
    8 用戶交互事件(比如鼠標點擊、滾動頁面、放大縮小等)
    
  2. 微任務:

    微任務是隨著ECMA標準升級提出的新的異步任務,微任務在異步任務隊列的基礎(chǔ)上增加了【微任務】的概念,每 ?個宏任務執(zhí)?前,程序會先檢測中是否有當次事件循環(huán)未執(zhí)?的微任務,優(yōu)先清空本次的微任務后,再執(zhí)?下? 個宏任務,每?個宏任務內(nèi)部可注冊當次任務的微任務隊列,再下?個宏任務執(zhí)?前運?,微任務也是按照進?隊列的順序執(zhí)?的。

    1. process.nextTick(node)
    2. MutationObserver(瀏覽器)
    3. promise.then/.catch/.finally
    

    【注】:new promise中的function是同步的,promise的.then.catch.finally中的內(nèi)容才是異步的

  3. 圖解

    主線程代碼在運?時,會按照同步和異步代碼將其分成兩個去處:如果是同步代碼執(zhí)?,就會直接將該任務放在?個叫做【函數(shù)執(zhí)??!康目臻g進?執(zhí)?,執(zhí)?棧是典型的【棧結(jié)構(gòu)】(先進后出),程序在運?的時候會將同步代碼按順序?棧。將異步代碼放到【?作線程】中暫時掛起,【?作線程】中保存的是定時任務函數(shù)、JS的交互事件、JS的?絡請求等耗時操作。

    當【主線程】將代碼塊篩選完畢后,進?執(zhí)?棧的函數(shù)會按照從外到內(nèi)的順序依次執(zhí)行,運?中涉及到的對象數(shù)據(jù)是在堆內(nèi)存中進?保存和管理的。當執(zhí)?棧內(nèi)的任務全部執(zhí)?完畢 后,執(zhí)?棧就會清空。

    執(zhí)?棧清空后,“事件循環(huán)”就會?作,“事件循環(huán)”會檢測本次循環(huán)中是否還有微任務執(zhí)行,若有的話,將所有微任務插入到任務隊列后執(zhí)行清空所有微任務。

    微任務清空后,?作線程會把到期的定時任務、返回數(shù)據(jù)的http 任務等【異步任務】按照先后順序插?到【任務隊列】中,等待執(zhí)?棧清空,再檢測本次的宏任務中注冊的所有微任務進行執(zhí)行清空。

代碼運行分區(qū)
宏任務微任務詳解
  1. 所以正確的一次 Event loop 順序是這樣的
  • 執(zhí)行同步代碼,這屬于宏任務
  • 執(zhí)行棧為空,查詢本次是否有微任務需要執(zhí)行
  • 執(zhí)行所有微任務
  • 必要的話渲染 UI
  • 然后開始下一輪 Event loop,執(zhí)行宏任務-微任務的循環(huán)
  1. 關(guān)于執(zhí)行棧

先進后出,后進先出。無限制的遞歸會導致執(zhí)行棧溢出,eg:

var i = 0;
function task(){
 i++
 console.log(`遞歸了${i}次`)
 task()
}
task()
棧溢出

跨越遞歸限制:

var i = 0;
function task(){
 i++
 console.log(`遞歸了${i}次`)
 //使?異步任務來阻?遞歸的溢出
 setTimeout(function(){
 task()
 },0)
}
task()

使用異步任務進行調(diào)用,會先把異步內(nèi)容掛起,等待執(zhí)行棧清空再進行異步內(nèi)容的入棧執(zhí)行和出棧,依次無限循環(huán)

  1. 特殊任務

關(guān)于requestAnimationFrame函數(shù)的執(zhí)?頻率是每秒鐘60次左右,是根據(jù)瀏覽器相關(guān)的,所以和其他timer類的宏任務的執(zhí)行時機不確定,可能在之前可能在之后。

setTimeout(function() {console.log('timer1')}, 0)
requestAnimationFrame(function(){
 console.log('UI update')
})
setTimeout(function() {console.log('timer2')}, 0)
new Promise(function executor(resolve) {
 console.log('promise1')
 resolve()
 console.log('promise2')
}).then(function() {
 console.log('promise.then')
})
console.log('end')
// 結(jié)果:
// promise1  promise2  end  promise.then  (UI update)  timer1  timer2  (UI update)

關(guān)于事件

document.addEventListener('click', function(){
 Promise.resolve().then(()=> console.log(1));
 console.log(2);
})
document.addEventListener('click', function(){
 Promise.resolve().then(()=> console.log(3));
 console.log(4);
})
// 2 1 4 3

解析:當元素被點擊時,兩個事件會按照先后的注冊順序放?異步任務隊列中進?執(zhí)?,所以事件1和事件2 會按照代碼編寫的順序觸發(fā)。由于事件執(zhí)?時并不會阻斷JS默認代碼的運?,所以事件任務也是異步任務,并且是宏 任務,所以兩個事件相當于按順序執(zhí)?的兩個宏任務

即順序:同步代碼(掛起click事件異步代碼)-> 點擊觸發(fā) -> 執(zhí)行第一個異步代碼宏任務(其中先執(zhí)行同步代碼2再微任務1) -> 執(zhí)行第二個異步代碼宏任務(其中先執(zhí)行同步代碼4再微任務3)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容