js 宏任務和微任務


寫在前面:壓力只是暫時的,都會過去,這是我一周以為聽到的最頓悟的一句話了吧~

  1.引言

  js作為單線程的運行機制,必定有自己的運行順序,在聽了一次分享后,也好奇這種運行的機制到底是什么?

  js可分為同步任務和異步任務,對於同步的任務,我們當然知道按照順序進行執行,但是對於異步的操作,會有一個優先級的執行順序,分別為宏任務和微任務

宏任務(macrotasks)和微任務(microtasks)??包含什么?

    macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
    microtasks: process.nextTick, Promises, Object.observe(廢棄), MutationObserver

 在js執行時候,一個主線程里面都會有一個事件循環(消息循環|運行循環)和事件隊列,存放各種要處理的事件信息,通過這個循環不斷處理這些事件信息或消息。

談到這里,很明顯知道,其實出現宏任務和微任務和瀏覽器以及js的執行機制有很大的關系。了解js的執行機制

2.javascript的執行runtime

 

 

 這是一張javascript的執行機制圖

JavaScript的運行分為

  (1) javaScript Engine,Chrome 的引擎就是 V8

  (2) Web APIs,DOM 的操作,AJAX,Timeout 等實際上調用的都是這里提供的

  (3)Callback Queue,回調的隊列,也就是剛剛所有的 Web APIs 里面的回調函數,實際上都是放在這里排隊的

  (4) EventLoop,事件循環,也就是剛所說的宏任務和微任務的容器

call Stack

      本身就是一個調用棧(就像瀏覽器中的JavaScript解釋器),追蹤函數執行流的一種機制,當執行環境調用了多個函數時,通過調用棧,我們可以追蹤到哪一個函數在執行,執行的函數體中又調用了哪些函數。

             每調用一個函數,解釋器就會把該函數添加進調用棧並開始執行。

            正在調用棧中執行的函數還調用了其它函數,那么新函數也將會被添加進調用棧,一旦這個函數被調用,便會立即執行。

             當前函數執行完畢后,解釋器將其清出調用棧,繼續執行當前執行環境下的剩余的代碼。

             當分配的調用棧空間被占滿時,會引發“堆棧溢出”。

    是存放執行的重要條件,也是因為只有一個調用棧,所以被稱為單線程

callback quene

   在js的編譯階段,將一些時間防止在執行隊列中

EventLoop 事件循環

    一個作用就是將callback quene隊列里的執行事件放在在call stack中,執行

3.事件循環

  js是單線程的,執行較長的js時候,頁面會卡死,無法響應,但是所有的操作都會被記住到另外的隊列。比如:點擊了一個元素,不會立刻的執行,但是等到js加載完畢后就會執行剛才點擊的操作,能夠知道有一個隊列記錄了所有有待執行的操作,這個隊列分為微觀和宏觀。微觀會比宏觀執行得更快。

   事件循環就是多線程的一種工作方式,Chrome里面是使用了共享的task_runner對象給自己和其它線程post task過來存起來,用一個死循環不斷地取出task執行,或者進入休眠等待被喚醒。Mac的Chrome渲染線程和瀏覽器線程還借助了Mac的sdk Cococa的NSRunLoop來做為UI事件的消息源。Chrome的多進程通信(不同進程的IO線程的本地socket通信)借助了libevent的事件循環,並加入了到了主消息循環里面。

  稱為事件循環的原因大多來源於源碼

 

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

  可以看到入伏哦有消息就會執行,沒消息就會繼續等待

消息執行完成
由於js單線程,每一個消息完整的執行后才會執行其他的消息,不會被其他的消息搶占,這樣的缺點就是當一個消息執行的特別久的時候web用戶無法處理用戶的交互,比如點擊或者滾動等,因此需要縮短消息的處理時間,將一個消息裁剪成多個消息。
添加消息
在瀏覽器里,當一個事件發生且有一個事件監聽器綁定在該事件上時,消息會被隨時添加進隊列。如果沒有事件監聽器,事件會丟失。所以點擊一個附帶點擊事件處理函數的元素會添加一個消息,其它事件類似。比如事件委托,監聽,可隨時放入消息隊列
setTimeout 接受一個參數,待加入隊列的消息和一個延遲,延遲代表了消息加入隊列的最小延遲事件,如果隊列中沒有其他的消息,在這段延遲時間過了后,消息會被立刻處理,但是如果有其他的消息,必須等到其他的消息處理完,因此,延遲時間表示最少延遲時間
 

 

4.宏任務和微任務 

  是一種人們定義的執行名字,

 

如何區分宏任務和微任務呢?划分的標准是什么

宏任務本質:參與了事件循環的任務。
回到 Chromium 中,需要處理的消息主要分成了三類:
  • Chromium 自定義消息
  • Socket 或者文件等 IO 消息
  • UI 相關的消息
1. 與平台無關的消息,例如 setTimeout 的定時器就是屬於這個
2.Chromium 的 IO 操作是基於 libevent 實現,它本身也是一個事件驅動的庫
3.UI 相關的其實屬於 blink 渲染引擎過來的消息,例如各種 DOM 的事件
其實與 JavaScript 的引擎無關,都是在 Chromium 實現的。

微任務本質:直接在 Javascript 引擎中的執行的,沒有參與事件循環的任務。
  1. 是個內存回收的清理任務,使用過 Java 的童鞋應該都很熟悉,只是在 JavaScript 這是V8內部調用的
  2. 就是普通的回調,MutationObserver 也是這一類
  3. Callable
  4. 包括 Fullfiled 和 Rejected 也就是 Promise 的完成和失敗
  5. Thenable 對象的處理任務
宏任務,微任務的優先級
promise 是在當前腳本代碼執行完后,立刻執行的,它並沒有參與事件循環,所以它的優先級是高於 setTimeout。
宏任務和微任務的總結:
  • 宏任務 Macrotasks 就是參與了事件循環的異步任務。
  • 微任務 Microtasks 就是沒有參與事件循環的“異步”任務。
  • console.log("開始執行1")
    console.log(Object.keys({a: 1}));
    setTimeout(() => {
        console.log(Object.keys({b: 2}));
        var promise = new Promise((resolve, reject) => {
            resolve(1);
        });
        promise.then(res => {
            //后續增加測試
            setTimeout(() => {
                console.log(Object.keys({e:1}))
            }, 0);
            console.log(Object.keys({c: 1}));
        });
    }, 2000);
    console.log("結束執行2")
    

      

     

     微觀任務是在當前JS調用執行完了之后立刻執行的,是同步的,在同一個調用棧里,沒有多線程異步,如這里包括promise.then在內的setTimeout回調里的代碼都是在DOMTimer.Fired執行的,只是說then被放到了當前要執行的整一個異步回調函數的最后面執行。所以setTimeout 0是給主線程的消息循環任務隊列添加了一個新的task(回調),而promise.then是在當前task的V8里的microtask插入了一個任務。那么肯定是當前正在執行的task執行完了才執行下一個task.vue的nextTick 也是一個微觀任務

除此之外的onload事件
let img = new Image(); 
img.src = 'image01.png?_=' + Date.now(); 
img.onload = function () { console.log('img ready'); } console.log(Object.keys({e: 1}));
微觀任務是不屬於事件循環的,它是V8的一個實現,用來實現Promise的then/reject,以及其它一些需要同步延后的callback,本質上它和當前的V8調用棧是同步執行的,只是放到了最后面。除了Promise/MutationObserver,在JS里面發起的請求也會創建一個微觀任務延后執行。
參考網站:

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM