1. 程式人生 > >JavaScript同步、非同步及事件迴圈

JavaScript同步、非同步及事件迴圈

更多文章

同步、非同步

JS是單執行緒的,每次只能做一件事情。像以下這種情況,程式碼會按順序執行,這個就叫同步。

console.log(1);
console.log(2);
console.log(3);

 

以下程式碼會輸出2、3、1,像這種不按順序執行的,或者說程式碼執行中間有時間間隙的,叫非同步。

setTimeout(() => {
    console.log(1);
}, 0);
console.log(2);
console.log(3);

 

事件迴圈

一個瀏覽器通常有以下幾個常駐的執行緒:

  • 渲染引擎執行緒:該執行緒負責頁面的渲染
  • JS引擎執行緒:負責JS的解析和執行
  • 定時觸發器執行緒:處理定時事件,比如setTimeout, setInterval
  • 事件觸發執行緒:處理DOM事件
  • 非同步http請求執行緒:處理http請求

渲染執行緒和JS引擎執行緒是不能同時進行的。也就是說在執行程式碼時,渲染會掛起;渲染DOM時,程式碼也不會執行。 雖然JS是單執行緒,但是瀏覽器是多執行緒的,在遇到像setTimeout、DOM事件、ajax等這種任務時,會轉交給瀏覽器的其他工作執行緒(上面提到的幾個執行緒)執行,執行完之後將回調函式放入到任務佇列。

// eventLoop是一個用作佇列的陣列
// (先進,先出)
var eventLoop = [ ]; var event; // “永遠”執行 while (true) { // 一次tick if (eventLoop.length > 0) { // 拿到佇列中的下一個事件 event = eventLoop.shift(); // 現在,執行下一個事件 event(); } }

 

我們可以用上面的程式碼來想像一下JS的執行情況。
JS主執行緒,就像是一個while迴圈,會一直執行下去。在這期間,每次都會檢視任務佇列有沒有需要執行的任務(回撥函式)。在執行完一個任務之後, 會繼續下一個迴圈,直到任務佇列所有任務都執行完為止。

microtask(微任務)、macrotask(巨集任務)

任務佇列又分微任務佇列和巨集任務佇列

微任務

  • Promise
  • MutationObserver(Mutation Observer API 用來監視 DOM 變動)
  • Object.observe()(已廢棄)

巨集任務

  • setTimeout
  • setInterval
  • setImmediate
  • I\O
  • UI rendering(DOM event)

執行過程

  1. 在JS執行完同步任務之後,會開始執行微任務佇列
  2. 在將所有的微任務執行完之後,會開始執行巨集任務佇列
  3. 在執行完一個巨集任務之後,跳出來,重新開始下一個迴圈(從1開始執行)

也就是說執行微任務佇列 會將佇列中的所有微任務執行完 而執行巨集任務佇列 每次只執行一個巨集任務 然後重新開始下一個迴圈 我們可以看看以下程式碼

setTimeout(() => {
    console.log(3)
    new Promise((resolve, reject) => {
        console.log(5)
        resolve()
    }).then(console.log(6))
}, 0)

setTimeout(() => {
    console.log(4)
}, 0)

new Promise((resolve, reject) => {
    console.log(1)
    resolve()
}).then(console.log(2))

 

輸出是1 2 3 5 6 4

我們來分析一下程式碼的執行過程

  1. 前面的兩個setTimeout都是巨集任務,所以現在巨集任務佇列有2個任務
  2. Promise裡面的程式碼是同步任務,所以現在會馬上執行 輸出1
  3. Promise的then是微任務,所以現在微任務佇列有1個任務
  4. 在執行完同步任務之後,開始執行微任務,也就是console.log(2), 輸出2
  5. 在執行完微任務之後,會執行巨集任務,第一個巨集任務也就是第一個setTimeout
  6. 第一個setTimeout會先輸出3,然後輸出5,因為這兩個都是同步任務,然後遇到then,加入微任務佇列,巨集任務執行完重新開始下一個迴圈。
  7. 因為沒有同步程式碼,所以接著執行微任務,此時微任務佇列有1個任務(第6步加入), 巨集任務佇列還有1個任務(第6步執行完了第一個巨集任務)
  8. 執行微任務,輸出6
  9. 再執行巨集任務,輸出4

eventloop