通殺 Event Loop 面試題

03-JS單線程, event loop完全搞懂代碼執行順序

瀏覽器工做原理

單線程的含義

瀏覽器是 multi-process,一個瀏覽器只有一個 Browser Process,負責管理 Tabs、協調其餘 process 和 Renderer process 存至 memory 內的 Bitmap 繪製到頁面上的(pixel);在 Chrome中,一個 Tab 對應一個 Renderer Process,Renderer process 是 multi-thread,其中 main thread 負責頁面渲染(GUI render engine)執行 JS (JS engine)和 event loop;network component 能夠開2~6個 I/O threads 平行去處理。node


Structure of a Web Browser

image.png

主線程,JS執行線程,UI渲染線程關係以下圖所示:



瀏覽器中的 JavaScript 執行機制

可視化演繹

深刻演示:loupegit

https://github.com/latentflip/loupegithub


// 函數執行棧演繹-->函數調用過程面試

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();複製代碼

兩個問題

問題1:若是咱們在瀏覽器控制檯中運行'foo'函數,是否會致使堆棧溢出錯誤?promise


function foo() {
  setTimeout(foo, 0); // 是否存在堆棧溢出錯誤?
};複製代碼


function foo() {
  foo() // 是否存在堆棧溢出錯誤?
};
foo();複製代碼


問題2:若是在控制檯中運行如下函數,頁面(選項卡)的 UI 是否仍然響應瀏覽器


function foo() {
  return Promise.resolve().then(foo);
};複製代碼

基礎題


alert(x); 
 
var x = 10;
alert(x); 
 
x = 20;
 
function x() {};
 
alert(x); 複製代碼


瀏覽器端的 Event Loop

一個函數執行棧、一個事件隊列和一個微任務隊列。bash

每從事件隊列中取一個事件時有微任務就把微任務執行完,而後纔開始執行事件架構


宏任務和微任務

宏任務,macrotask,也叫tasks。 一些異步任務的回調會依次進入macro task queue,等待後續被調用,這些異步任務包括:異步

  • setTimeout
  • setInterval
  • setImmediate (Node獨有)
  • requestAnimationFrame (瀏覽器獨有)
  • I/O
  • UI rendering (瀏覽器獨有)

微任務,microtask,也叫jobs。 另外一些異步任務的回調會依次進入micro task queue,等待後續被調用,這些異步任務包括:ide

  • process.nextTick (Node獨有)
  • Promise.then()
  • Object.observe
  • MutationObserver

(注:這裏只針對瀏覽器和NodeJS)

注意:Promise構造函數裏的代碼是同步執行的。

基礎題

setTimeout(()=> {
    console.log(1)
    Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(()=> {
    console.log(2)
}, 0)複製代碼

可視化演繹

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});複製代碼

瀏覽器端:jakearchibald.com/2015/tasks-…

鞏固提升題

console.time("start")

setTimeout(function () {
    console.log(2);
}, 10);

new Promise(function (resolve) {
    console.log(3);
    resolve();
    console.log(4);
}).then(function () {
    console.log(5);
    console.timeEnd("start")
});
console.log(6);
console.log(8);
requestAnimationFrame(() => console.log(9))複製代碼

Node.js 架構圖

image.png

Node.js 中的 Event Loop


node_eventloop.png


Node.js的Event Loop過程:

  1. 執行全局Script的同步代碼
  2. 執行microtask微任務,先執行全部Next Tick Queue中的全部任務,再執行Other Microtask Queue中的全部任務
  3. 開始執行macrotask宏任務,共6個階段,從第1個階段開始執行相應每個階段macrotask中的全部任務,注意,這裏是全部每一個階段宏任務隊列的全部任務,在瀏覽器的Event Loop中是隻取宏隊列的第一個任務出來執行,每個階段的macrotask任務執行完畢後,開始執行微任務,也就是步驟2
  4. Timers Queue -> 步驟2 -> I/O Queue -> 步驟2 -> Check Queue -> 步驟2 -> Close Callback Queue -> 步驟2 -> Timers Queue ......
  5. 這就是Node的Event Loop【簡化版】



瀏覽器端和 Node 端有什麼不一樣

  1. 瀏覽器的Event Loop和Node.js 的Event Loop是不一樣的,實現機制也不同,不要混爲一談。
  2. Node.js 能夠理解成有4個宏任務隊列和2個微任務隊列,可是執行宏任務時有6個階段。
  3. Node.js 中,先執行全局Script代碼,執行完同步代碼調用棧清空後,先從微任務隊列Next Tick Queue中依次取出全部的任務放入調用棧中執行,再從微任務隊列Other Microtask Queue中依次取出全部的任務放入調用棧中執行。而後開始宏任務的6個階段,每一個階段都將該宏任務隊列中的全部任務都取出來執行(注意,這裏和瀏覽器不同,瀏覽器只取一個),每一個宏任務階段執行完畢後,開始執行微任務,再開始執行下一階段宏任務,以此構成事件循環。
  4. MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(瀏覽器)、IO、UI rendering
  5. Microtask包括: process.nextTick(Node)、Promise.then、Object.observe、MutationObserver


注意:new Promise() 構造函數裏面是同步代碼,而非微任務。

面試常考細節

微任務有兩種 nextTick和 then 那麼這兩個誰快呢?

Promise.resolve('123').then(res=>{  console.log(res)})
process.nextTick(() => console.log('nextTick'))複製代碼

//順序 nextTick 123

//很明顯 nextTick快

解釋:

promise.then 雖然和 process.nextTick 同樣,都將回調函數註冊到 microtask,但優先級不同。process.nextTick 的 microtask queue 老是優先於 promise 的 microtask queue 執行。

setTimeout 和 setImmediate

setImmediate(callback[, ...args])

Schedules the "immediate" execution of the callback after I/O events' callbacks.

setImmediate()方法用於中斷長時間運行的操做,並在完成其餘操做後當即運行回調函數。

setTimeout 和 setImmediate 執行順序不固定 取決於node的準備時間

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

setImmediate(() => {
    console.log('setImmediate')
})複製代碼

運行結果:

setImmediate

setTimeout

或者:

setTimeout

setImmediate


爲何結果不肯定呢?

解釋:

setTimeout/setInterval 的第二個參數取值範圍是:[1, 2^31 - 1],若是超過這個範圍則會初始化爲 1,

即 setTimeout(fn, 0) === setTimeout(fn, 1)。

咱們知道 setTimeout 的回調函數在 timer 階段執行,setImmediate 的回調函數在 check 階段執行,event loop 的開始會先檢查 timer 階段,可是在開始以前到 timer 階段會消耗必定時間;

因此就會出現兩種狀況:

  1. timer 前的準備時間超過 1ms,知足 loop->time >= 1,則執行 timer 階段(setTimeout)的回調函數
  2. timer 前的準備時間小於 1ms,則先執行 check 階段(setImmediate)的回調函數,下一次 event loop 執行 timer 階段(setTimeout)的回調函數。
setTimeout(() => {
    console.log('setTimeout')
}, 0)

setImmediate(() => {
    console.log('setImmediate')
})

const start = Date.now()
while (Date.now() - start < 10);複製代碼

運行結果必定是:

setTimeout

setImmediate


const fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('setTimeout')
    }, 0)

    setImmediate(() => {
        console.log('setImmediate')
    })
})複製代碼

運行結果:

setImmediate

setTimeout

解釋:

fs.readFile 的回調函數執行完後:

註冊 setTimeout 的回調函數到 timer 階段

註冊 setImmediate 的回調函數到 check 階段

event loop 從 pool 階段出來繼續往下一個階段執行,剛好是 check 階段,因此 setImmediate 的回調函數先執行

本次 event loop 結束後,進入下一次 event loop,執行 setTimeout 的回調函數

因此,在 I/O Callbacks 中註冊的 setTimeout 和 setImmediate,永遠都是 setImmediate 先執行。

鞏固提升題目

console.time("start")

setTimeout(function () {
    console.log(2);
}, 10);
setImmediate(function () {
    console.log(1);
});

new Promise(function (resolve) {
    console.log(3);
    resolve();
    console.log(4);
}).then(function () {
    console.log(5);
    console.timeEnd("start")
});
console.log(6);
process.nextTick(function () {
    console.log(7);
});
console.log(8);
// requestAnimationFrame(() => console.log(9))
複製代碼

運行結果以下:

image.png

運行時分析

image.png


Node 11.x + 新變化


setTimeout(() => console.log('timeout1'));
setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => console.log('promise resolve'))
});
setTimeout(() => console.log('timeout3'));
setTimeout(() => console.log('timeout4'));複製代碼

瀏覽器執行結果:

image.png

低於Node 11的版本

image.png

Node 11+

image.png

向瀏覽器運行結果靠齊

image.png

image.png

參考資料:

github.com/nodejs/node… MacroTask and MicroTask execution order

blog.insiderattack.net/new-changes…

github.com/nodejs/node… timers: run nextTicks after each immediate and timer

技術交流羣:668337232


掃碼聯繫我哦~

相關文章
相關標籤/搜索