[面試專題]JS異步原理(事件,隊列)

JS異步原理(事件,隊列)


調用棧

  • JS執行時會造成調用棧,調用一個函數時,返回地址、參數、本地變量都會被推入棧中,若是當前正在運行的函數中調用另一個函數,則該函數相關內容也會被推入棧頂.該函數執行完畢,則會被彈出調用棧.變量也隨之彈出,因爲複雜類型值存放於堆中,所以彈出的只是指針,他們的值依然在堆中,由GC決定回收.html

  • 尾調用:指某個函數的最後一步是調用另外一個函數。由調用棧可知,調用棧中有a函數,若是a函數調用b函數,則b函數也隨之入棧,此時棧中就會有兩個函數.可是若是b函數是a函數最後一步,而且不需保留外層函數調用記錄,即a函數調用位置變量等都不須要用到,則該調用棧中會只保留b函數,這就叫作"尾調用優化"(Tail call optimization),即只保留內層函數的調用記錄。若是全部函數都是尾調用,那麼徹底能夠作到每次執行時,調用記錄只有一項,這將大大節省內存。這就是"尾調用優化"的意義。node

function a() {
          let m = 1;
          let n = 2;
          return b(m + n);
        }
        a();
        
        // 等同於
        function a() {
          return b(3);
        }
        a();
        
        // 等同於
        b(3);

事件循環(event loop)和任務隊列(task queue)

  • JS的異步機制由事件循環和任務隊列構成.JS自己是單線程語言,所謂異步依賴於瀏覽器或者操做系統等完成. JavaScript 主線程擁有一個執行棧以及一個任務隊列,主線程會依次執行代碼,當遇到函數時,會先將函數入棧,函數運行完畢後再將該函數出棧,直到全部代碼執行完畢。瀏覽器

  • 遇到異步操做(例如:setTimeout, AJAX)時,異步操做會由瀏覽器(OS)執行,瀏覽器會在這些任務完成後,將事先定義的回調函數推入主線程的任務隊列(task queue)中,當主線程的執行棧清空以後會讀取task queue中的回調函數,當task queue被讀取完畢以後,主線程接着執行,從而進入一個無限的循環,這就是事件循環.異步

However, we only have one main thread and one call-stack, so in case there is another request being served when the said file is read, its callback will need to wait for the stack to become empty. The limbo where callbacks are waiting for their turn to be executed is called the task queue (or event queue, or message queue). Callbacks are being called in an infinite loop whenever the main thread has finished its previous task, hence the name 'event loop'.函數

Microtask 與 Macrotask

  • 一個瀏覽器環境(unit of related similar-origin browsing contexts.)只能有一個事件循環(Event loop),而一個事件循環能夠多個任務隊列(Task queue),每一個任務都有一個任務源(Task source)。例如,客戶端可能實現了一個包含鼠標鍵盤事件的任務隊列,還有其餘的任務隊列,而給鼠標鍵盤事件的任務隊列更高優先級,例如75%的可能性執行它。這樣就能保證流暢的交互性,並且別的任務也能執行到了。可是,同一個任務隊列中的任務必須按先進先出的順序執行。多個任務隊列,是爲了方便控制優先級。任務隊列是一個先進先出的隊列.oop

  • macrotask 和 microtask 是異步任務的兩種分類。在掛起任務時,JS 引擎會將全部任務按照類別分到這兩個隊列中,首先在 macrotask 的隊列(這個隊列也被叫作 task queue)中取出第一個任務,執行完畢後取出 microtask 隊列中的全部任務順序執行;以後再取 macrotask 任務,周而復始,直至兩個隊列的任務都取完。優化

  • 所有代碼(script)是一個macrotask,js先執行一個macrotask,執行過程當中遇到(setTimeout, setInterval, setImmediate等)異步操做則建立一個macrotask,遇到(process.nextTick, Promises等)建立一個microtask,這兩個queue分別被掛起.執行棧爲空時開始處理macrotask,完成後處理microtask,直到該microtask所有執行完,而後繼續主線程調用棧.spa

注:每一次事件循環(one cycle of the event loop),只處理一個 (macro)task。待該 macrotask 完成後,全部的 microtask 會在同一次循環中處理。處理這些 microtask 時,還能夠將更多的 microtask 入隊,它們會一一執行,直到整個 microtask 隊列處理完。
異步示意圖操作系統

兩個類別的具體分類以下:線程

macro-task: script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, Promises(這裏指瀏覽器實現的原生 Promise), Object.observe, MutationObserver

參考文章:
Promise的隊列與setTimeout的隊列有何關聯?
node事件循環
深刻淺出JavaScript事件循環機制(下)

相關文章
相關標籤/搜索