JS中的同步異步編程,宏任務與微任務的執行順序

  • 2020 年 2 月 18 日
  • 筆記

首先我們先看看同步與異步的定義,及瀏覽器的執行機制,方便我們更好地理解同步異步編程。

  瀏覽器是多線程的,JS是單線程的(瀏覽器只分配一個線程來執行JS)

  進程大線程小:一個進程中包含多個線程,例如在瀏覽器中打開一個HTML頁面就佔用了一個進程,加載頁面的時候,瀏覽器分配一個線程去計算DOM樹,分配其它的線程去加載對應的資源文件…再分配一個線程去自上而下執行JS

  同步:在一個線程上(主棧/主任務隊列)同一個時間只能做一件事情,當前事情完成才能進行下一個事情(先把一個任務進棧執行,執行完成,在把下一個任務進棧,上一個任務出棧…)

  異步:在主棧中執行一個任務,但是發現這個任務是一個異步的操作,我們會把它移除主棧,放到等待任務隊列中(此時瀏覽器會分配其它線程監聽異步任務是否到達指定的執行時間),如果主棧執行完成,監聽者會把到達時間的異步任務重新放到主棧中執行…

  [宏任務:macro task]

– 定時器

– 事件綁定

– ajax

– 回調函數

– Node中fs可以進行異步的I/O操作

  [微任務:micro task]

– Promise(async/await) => Promise並不是完全的同步,當在Excutor中執行resolve或者reject的時候,此時是異步操作,會先執行then/catch等,當主棧完成後,才會再去調用resolve/reject把存放的方法執行

– process.nextTick (node中實現的api,把當前任務放到主棧最後執行,當主棧執行完,先執行nextTick,再到等待隊列中找)

   – MutationObserver (創建並返回一個新的 MutationObserver 它會在指定的DOM發生變化時被調用。)

  執行順序優先級:SYNC => MICRO => MACRO

所有JS中的異步編程僅僅是根據某些機制來管控任務的執行順序,不存在同時執行兩個任務這一說法

先來看一個例子:

setTimeout(() => {      console.log(1);  }, 20);    setTimeout(() => {      console.log(2);  }, 0);//=>默認會有最小的等待時間(V8一般是5~6MS)    console.time('WHILE');  let i = 0;  while (i <= 99999999) {      i++;  }  console.timeEnd('WHILE');    setTimeout(() => {      console.log(3);  }, 10);    console.log(4);

結果輸出如圖:

我們先模擬下瀏覽器的程序執行過程,代碼自上而下執行,碰到第一個程序,先放入主棧(主任務隊列),此時瀏覽器發現這是一個宏任務定時器,把它移出主棧,放入等待任務隊列,再繼續執行下面的代碼,放入主棧執行,發現第二個任務也是宏任務的定時器,放入等待隊列,繼續往下執行,推入主棧,同步任務,循環99999999次之後輸出次數,再執行下一個程序,也移入等待隊列,再執行代碼,發現是同步任務,輸出4,此時主棧空閑,任務隊列到達時間後先進先出的原則,首先第二個任務到達時間,把它放入主棧執行,輸出2,此時本因輸出3,因為第三個程序是10ms到達,第一個是20s到達,但是第三個程序是等待247.849853515625ms後才放入的等待隊列,所以第一個程序先到達,輸出1,最後輸出3。

我們用ajax來看看js的同步與異步的執行順序和機制,AJAX任務開始:SEND,AJAX任務結束:狀態為4

let xhr = new XMLHttpRequest();  xhr.open('GET', 'xxx.txt', false);  // 放到等待區的時候,此時狀態是1  xhr.onreadystatechange = () => {      console.log(xhr.readyState);//=>4  };  xhr.send();  // 同步ajax,xhr.send時為同步,xhr.send()執行完後狀態為4,任務狀態為4的時候主棧空閑,onreadystatechange監聽到狀態變化,輸出4  
 let xhr = new XMLHttpRequest();   xhr.open('GET', 'xxx.txt', false);   xhr.send();  // 狀態已經為4了   xhr.onreadystatechange = () => {//=>狀態改變才會觸發,放到等待區的時候狀態已經為4了,不會在改變了,所以不會執行這個方法(啥都不會輸出)       console.log(xhr.readyState);   };
 let xhr = new XMLHttpRequest();   xhr.open('GET', 'xxx.txt');   xhr.send();//=>異步操作:執行SEND後,有一個線程是去請求數據,主棧會空閑下來  // 放等待區之前狀態是1   xhr.onreadystatechange = () => {       console.log(xhr.readyState);//=> 2 3 4   };  // 主棧又空閑了  // 狀態為2  把函數執行  // 狀態為3  把函數執行  // 狀態為4  把函數執行