js異步編程,eventLoop、消息隊列都是作什麼的? 什麼是宏任務,什麼是微任務

單線程的JavaScript

JavaScript是一門單線程語言,原由是設計之初js只用來操做dom,對錶單進行簡單的校驗。在這種執行環境簡單的狀況下,天然就選擇了單線程來處理程序。可是單線程若是遇到執行時間較長的程序片斷,會拖延甚至阻塞程序的執行,對於用戶來講,頁面呈現"卡死狀態",這是最糟糕的體驗。node

​ 爲了解決上述問題,JavaScript將程序的執行分爲同步和異步。面試

在JavaScript中寫異步代碼也叫作異步編程,進行異步編程的方式有:編程

  • 回調函數promise

  • 事件監聽瀏覽器

  • 發佈訂閱markdown

  • promise多線程

維護異步任務和執行異步任務

異步即未來, 異步任務就是未來執行的任務dom

js引擎是單線程的,那麼異步任務是如何維護的呢?異步

js引擎負責解析並編譯js代碼。制定做用域標準,分配內存,建立執行上下文調用棧...。編譯好的代碼放到運行環境中去運行,而運行環境會維護異步任務。async

對瀏覽器而言,瀏覽器是多線程的,它能夠分配線程去倒計時定時器,發送請求,事件監聽等。當定時器中的事件倒計時完畢,將其扔到也是由瀏覽器維護的消息隊列中,當遇到其餘異步時,瀏覽器會分配進程去處理,處理完畢也是扔到消息隊列中。等待js引擎去執行。

以上所說的異步任務均是宏任務。

關於異步還有一些有趣的事情。

有趣的異步控制檯

console也並不是js標準,沒有具體的約束和規則去指定console的行爲,console的行爲是由運行環境決定的。

摘自你不知道的JavaScript(中卷) p141

不一樣的瀏覽器和 JavaScript 環境能夠按照本身的意願來實現,有時候這會引發混淆。 尤爲要提出的是,在某些條件下,某些瀏覽器的 console.log(..) 並不會把傳入的內容立 即輸出。出現這種狀況的主要緣由是,在許多程序(不僅是 JavaScript)中,I/O 是很是低 速的阻塞部分。因此,(從頁面 /UI 的角度來講)瀏覽器在後臺異步處理控制檯 I/O 可以提 高性能,這時用戶甚至可能根本意識不到其發生。

console一般是進行程序調試寫的比較多,若是產生了一些迷惑性行爲,能夠這樣作

最好的選擇是在 JavaScript 調試器中使用斷點, 而不要依賴控制檯輸出。次優的方案是把對象序列化到一個字符串中,以強制執行一次「快照」,好比經過 JSON.stringify(..)。

當檢測到js主線程中的調用棧爲空時(主線程會維護一個巨大的匿名函數,這個匿名函數用來執行js代碼)。 瀏覽器早已提供好了用做事件觸發的線程,事件觸發線程從消息隊列中按照隊列排序取出一個任務放到執行棧中壓棧執行。

執行過程當中遇到的問題:

  1. 若是遇到宏任務: 將其給瀏覽器進行處理,處理完畢放入消息隊列中排隊。

  2. 若是遇到微任務:將其放到微任務隊列中,依次執行,不用去排隊。也就是微任務是能夠"插隊"的。等到微任務隊列中的全部微任務所有執行完畢。纔會開啓下一輪事件循環。

  • 微任務的出現讓js的異步處理更加靈活,高效。
  • 例如修改dom: 若是在微任務中修改dom,則在此次事件循環中就能夠看到修改後的結果,若是在宏任務中修改,則只能在下次事件循環中看到修改後的結果了。
  • 微任務有微任務消息隊列,宏任務有宏任務消息隊列[事件隊列存放的是異步事件返回結果],可是js的事件循環是惟一一個。
宏任務和微任務有哪些?

宏任務有: scriptsetTimeoutsetIntervalsetImmediate(瀏覽器暫時不支持,只有IE10支持)、I/OUI Rendering

微任務有: Process.nextTick(node)PromiseMutationObserver

模擬執行一個宏任務

setTimeout(() => {
    console.log('setTimeout');
}, 0);
複製代碼

模擬執行一個微任務

queueMicrotask(() => {
    console.log('queueMicrotask');
}); 
複製代碼

上面兩種方式不會建立額外的對象,不會形成浪費(好比經過promise

建立微任務.)

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')

複製代碼
一道面試題

js編譯完成,進入瀏覽器執行環境開始執行。

遇到async1,async2函數分配內存

打印script start

遇到setTimeout,計時完成放到消息隊列中,等待主線程空閒時執行。

調用async1函數,打印async1 start 遇到awiait,同步代碼,執行後面的async2函數,打印async2(async await是generator的語法糖。能夠用generator來實現async await的效果。generator也是微任務隊列) 將後面的片斷放到微任務隊列中。

建立Promise實例對象,打印promise1, 將then函數扔到微任務隊列中。

打印script end

主線程空閒, 事件觸發線程拿到微任務隊列中的第一個任務,放到主線程中的調用棧中執行,打印async1 end

而後執行微任務隊列中的第二個微任務,打印promise2,微任務隊列清空,去宏任務隊列中拿到第一個任務,放到主線程中執行,打印 setTimeout

總結

在面試或者在寫代碼中,只要明白了這套規則,就瞭解了js的執行機制。對於一些執行機制以及執行順序的問題也就迎刃而解了。

相關文章
相關標籤/搜索