[面試題]事件迴圈經典面試題解析

CodeSpirit發表於2022-03-09

基礎概念

  1. 程式是計算機已經執行的程式,執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程式中.瀏覽器中每開一個Tab頁,就會開啟一個程式,而這個程式又包含了很多執行緒.
  2. 大家都知道JS是一門單執行緒語言,如果遇到了非常耗時的操作,那麼JS的執行就會受到阻塞,這肯定不是我們想看到的,所以這些耗時的操作,往往不是由JS執行緒所執行的,而是交由瀏覽器中的其他執行緒去完成的,成功之後只要在某個特定的時候進行一個回撥函式即可
  3. 所以引出了事件迴圈的概念,在事件迴圈中,分兩種任務,分別是巨集任務和微任務
    1. 巨集任務包含 ajax、setTimeout、setInterval、DOM監聽、UI Rendering
    2. 微任務包含 Promise的then回撥、 Mutation Observer API、queueMicrotask()等
  4. 接下來我們直接就開始練習面試題熟悉熟悉

面試題一

setTimeout(function () {
    console.log("setTimeout1");

    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        new Promise(function (resolve) {
            resolve();
        }).then(function () {
            console.log("then4");
        });
        console.log("then2");
    });
});

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("then1");
});

setTimeout(function () {
    console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
    console.log("queueMicrotask1")
});

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log("then3");
});
  1. 先解決同步任務
    1. 輸出promise1 2
  2. 開始解決非同步任務中的微任務
    1. 輸出then1 queueMicrotask1 then3
  3. 開始解決非同步任務中的巨集任務
    1. 輸出setTimeout1,在第一個定時器中,又遇到了微任務,那麼接著執行微任務
      1. 輸出then2 然後輸出 then4
    2. 目光跳出第一個定時器中,看到第二個定時器 開始輸出setTimeout2
  4. 最後的完整輸出為 promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2

面試題二

async function async1() {
  console.log('async1 start')
  // await非同步函式的返回結果 resolve的結果會作為整個非同步函式的promise的resolve結果->同步程式碼
  // await後面的執行程式碼 就會變成.then後面的執行函式->微任務
  // 也就是說  console.log('async1 end') 這一段是相當於then方法內的 會被加入微任務中
  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')
  1. 先執行同步程式碼
    1. 輸出script start async1 start async2 promise1 script end
  2. 開始執行微任務
    1. 輸出async1 end promise2
  3. 最後執行巨集任務
    1. 輸出setTimeout
  4. 完整輸出:script start async1 start async2 promise1 script end async1 end promise2 setTimeout

面試題三

Promise.resolve().then(() => {
  console.log(0);
  //1.直接返回4 微任務不會做任何延遲 
  // return 4
  //2.直接返回Promise.resolve(4) 微任務推遲兩次
  // return Promise.resolve(4);
  //3.返回thenable物件
  return {
    then: ((resolve, reject) => {
      resolve(4);
    })
  }
}).then((res) => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() => {
  console.log(6);
})

這道面試題有些特殊,需要大家記住兩個結論

  1. 如果返回的是thenable物件,那麼微任務會推遲一次,thenable物件就是實現了Promise.then的那個函式,具體可看程式碼
  2. 如果返回的是Promise.resolve(4),那麼微任務會推遲兩次,這個相當於是返回一個Promise之後又用了resolve,二者是等價的
  3. 為了配合大家理解,我給大家畫了幾張圖,大家可以看看

image.png
事件迴圈_return.gif事件迴圈_return_thenable_.gif事件迴圈_return_promise.resolve_.gif

面試題四

本道題是基於node的事件迴圈,和瀏覽器的事件迴圈不一樣,需要記住以下幾點

node的事件迴圈也分巨集任務和微任務

  • 巨集任務: setTimeout、setInterval、IO事件、setImmediate、close事件
  • 微任務: Promise的then回撥、process.nextTick、queueMicrotask

node的每次事件迴圈都是按照以下順序來執行的

  1. next tick microtask queue
  2. other microtask queue
  3. timer queue
  4. poll queue
  5. pcheck queue
  6. close queue
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('setTimeout0')
}, 0)

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')
  1. 首先執行同步任務
    1. 輸出script start async1 start async2 promise1 promise2 script end
  2. 接著執行微任務,因為node會優先執行nextTick這個微任務
    1. 所以先輸出nextTick1 nextTick2
    2. 在輸出其他微任務,輸出async1 end promise3
  3. 最後執行巨集任務
    1. 輸出 setTimeout0 setImmediate
    2. 因為這個定時器延時3ms執行,所以會讓其他的巨集任務先執行完畢,才回去執行這個定時器,所以最後輸出setTimeout2
  4. 最後的輸出結果: script start async1 start async2 promise1 promise2 script end nextTick1 nextTick2 async1 end promise3 setTimeout0 setImmediate setTimeout2

相關文章