日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

作者:趁你還年輕

轉發鏈接:https://segmentfault.com/a/1190000022950333

前言

看到這些詞仿佛比較讓人摸不著頭腦,其實在我們的日常開發中,早就和它們打過交道了。

我來舉幾個常見的例子:

  • 我執行了一段js,頁面就卡了挺久才有響應
  • 我觸發了一個按鈕的click事件,click事件處理器做出了響應
  • 我用setTimeout(callback, 1000)給代碼加了1s的延時,1秒里發生了很多事情,然后功能正常了
  • 我用setInterval(callback, 100)給代碼加了100ms的時間輪訓,直到期待的那個變量出現再執行后續的代碼,并且結合setTimeout刪除這個定時器
  • 我用Promise,async/await順序執行了異步代碼
  • 我用EventEmitter、new Vue()做事件廣播訂閱
  • 我用MutationObserver監聽了DOM更新
  • 我手寫了一個Event類做事件的廣播訂閱
  • 我用CustomEvent創建了自定義事件
  • 我·······

其實上面舉的這些click, setTimeout, setInterval, Promise,async/await, EventEmitter, MutationObserver, Event類, CustomEvent與多進程、單線程、事件循環、消息隊列、宏任務、微任務或多或少的都有所聯系。

而且也與瀏覽器的運行原理有一些關系,作為每天在瀏覽器里辛勤耕耘的前端工程師們,瀏覽器的運行原理(多進程、單線程、事件循環、消息隊列、宏任務、微任務)可以說是必須要掌握的內容了,不僅對面試有用,對手上負責的開發工作也有很大的幫助。

  • 淺談瀏覽器
  • 架構瀏覽器可以是哪種架構?
  • 如何理解Chrome的多進程架構?
  • 前端最核心的渲染進程包含哪些線程?
  • 主線程(Main thread)(下載資源、執行js、計算樣式、進行布局、繪制合成)
  • 光柵線程(Raster thread)
  • 合成線程(Compositor thread)
  • 工作線程(Worker thread)
  • 淺談單線程jsjs引擎圖什么是單線程js?
  • 單線程js屬于瀏覽器的哪個進程?
  • js為什么要設計成單線程的?
  • 事件循環與消息隊列什么是事件循環?
  • 什么是消息隊列?
  • 如何實現一個 EventEmitter(支持 on,once,off,emit)?
  • 宏任務和微任務哪些屬于宏任務?
  • 哪些屬于微任務?
  • 事件循環,消息隊列與宏任務、微任務之間的關系是什么?
  • 為任務添加和執行流程示意圖
  • 瀏覽器頁面循環系統原理圖消息隊列和事件循環setTimeoutXMLHttpRequest宏任務
  • 參考資料

淺談Chrome架構

瀏覽器可以是哪種架構?

瀏覽器本質上也是一個軟件,它運行于操作系統之上,一般來說會在特定的一個端口開啟一個進程去運行這個軟件,開啟進程之后,計算機為這個進程分配CPU資源、運行時內存,磁盤空間以及網絡資源等等,通常會為其指定一個PID來代表它。

先來看看我的機器上運行的微信和Chrome的進程詳情

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

如果自己設計一個瀏覽器,瀏覽器可以是哪種架構呢?

  • 單進程架構(線程間通信)
  • 多進程架構(進程間IPC通信)

如果瀏覽器單進程架構的話,需要在一個進程內做到網絡、調度、UI、存儲、GPU、設備、渲染、插件等等任務,通常來說可以為每個任務開啟一個線程,形成單進程多線程的瀏覽器架構。

但是由于這些功能的日益復雜,例如將網絡,存儲,UI放在一個線程中的話,執行效率和性能越來越低下,不能再向下拆分出類似“線程”的子空間

因此,為了逐漸強化瀏覽器的功能,于是產生了多進程架構的瀏覽器,可以將網絡、調度、UI、存儲、GPU、設備、渲染、插件等等任務分配給多個單獨的進程,在每一個單獨的進程內,又可以拆分出多個子線程,極大程度地強化了瀏覽器。

如何理解Chrome的多進程架構?

Chrome作為瀏覽器界里的一哥,它也是多進程IPC架構的。

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

Chrome多進程架構主要包括以下4個進程:

  • Browser進程(負責地址欄、書簽欄、前進后退、網絡請求、文件訪問等)
  • Renderer進程(負責一個Tab內所有和網頁渲染有關的所有事情,是最核心的進程
  • GPU進程(負責GPU相關的任務)
  • Plugin進程(負責Chrome插件相關的任務)

Chrome 多進程架構的優缺點優點

  • 每一個Tab就是要給單獨的進程
  • 由于每個Tab都有自己獨立的Renderer進程,因此某一個Tab出問題不會影響其它Tab

缺點

  • Tab間內存不共享,不同進程內存包含相同內容

Chrome多進程架構實錘圖

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

前端最核心的渲染(Renderer)進程包含哪些線程?

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

渲染進程主要包括4個線程:

  • 主線程(Main thread)(下載資源、執行js、計算樣式、進行布局、繪制合成)
  • 光柵線程(Raster thread)
  • 合成線程(Compositor thread)
  • 工作線程(Worker thread)

渲染進程的主線程知識點:

  • 下載資源:主線程可以通過Browser進程的network線程下載圖片,css,js等渲染DOM需要的資源文件
  • 執行JS:主線程在遇到<script>標簽時,會下載并且執行js,執行js時,為了避免改變DOM的結構,解析html停止,js執行完成后繼續解析HTML。正是因為JS執行會阻塞UI渲染,而JS又是瀏覽器的一哥,因此瀏覽器常常被看做是單線程的。
  • 計算樣式:主線程會基于CSS選擇器或者瀏覽器默認樣式去進行樣式計算,最終生成Computed Style
  • 進行布局:主線程計算好樣式以后,可以確定元素的位置信息以及盒模型信息,對元素進行布局
  • 進行繪制:主線程根據先后順序以及層級關系對元素進行渲染,通常會生成多個圖層
  • 最終合成:主線程將渲染后的多個frame(幀)合成,類似flash的幀動畫和PS的圖層

渲染進程的主線程細節可以查閱Chrome官方的博客:Inside look at modern web browser (part 3)和Rendering Performance

渲染進程的合成線程知識點:

  • 瀏覽器滾動時,合成線程會創建一個新的合成幀發送給GPU
  • 合成線程工作與主線程無關,不用等待樣式計算或者JS的執行,因此合成線程相關的動畫比涉及到主線程重新計算樣式和js的動畫更加流暢

下面來看下主線程、合成線程和光柵線程一起作用的過程1.主線程主要遍歷布局樹生成層樹

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

2.柵格線程柵格化磁貼到GPU

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

3.合成線程將磁貼合成幀并通過IPC傳遞給Browser進程,顯示在屏幕上

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

圖片引自Chrome官方博客:Inside look at modern web browser (part 3)

淺談單線程js

js引擎圖

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

什么是單線程js?

如果仔細閱讀過第一部分“談談瀏覽器架構”的話,這個答案其實已經非常顯而易見了。在”前端最核心的渲染進程包含哪些線程?“這里我們提到了主線程(Main thread)(下載資源、執行js、計算樣式、進行布局、繪制合成,注意其中的執行js,這里其實已經明確告訴了我們Chrome中JAVAScript運行的位置。

那么Chrome中JavaScript運行的位置在哪里呢?

渲染進程(Renderer Process)中的主線程(Main Thread)

單線程js屬于瀏覽器的哪個進程?

單線程的js -> 主線程(Main Thread)-> 渲染進程(Renderer Process)

js為什么要設計成單線程的?

其實更為嚴謹的表述是:“瀏覽器中的js執行和UI渲染是在一個線程中順序發生的。”

這是因為在渲染進程的主線程在解析HTML生成DOM樹的過程中,如果此時執行JS,主線程會主動暫停解析HTML,先去執行JS,等JS解析完成后,再繼續解析HTML。

那么為什么要“主線程會主動暫停解析HTML,先去執行JS,再繼續解析HTML呢”?

這是主線程在解析HTML生成DOM樹的過程中會執行style,layout,render以及composite的操作,而JS可以操作DOM,CSSOM,會影響到主線程在解析HTML的最終渲染結果,最終頁面的渲染結果將變得不可預見。

如果主線程一邊解析HTML進行渲染,JS同時在操作DOM或者CSSOM,結果會分為以下情況:

  • 以主線程解析HTML的渲染結果為準
  • 以JS同時在操作DOM或者CSSOM的渲染結果為準

考慮到最終頁面的渲染效果的一致性,所以js在瀏覽器中的實現,被設計成為了JS執行阻塞UI渲染型。

事件循環

什么是事件循環?

事件循環英文名叫做Event Loop,是一個在前端界老生常談的話題。我也簡單說一下我對事件循環的認識:

事件循環可以拆為“事件”+“循環”。先來聊聊“事件”:

如果你有一定的前端開發經驗,對于下面的“事件”一定不陌生:

  • click、mouseover等等交互事件
  • 事件冒泡、事件捕獲、事件委托等等
  • addEventListener、removeEventListener()
  • CustomEvent(自定義事件實現自定義交互)
  • EventEmitter、EventBus(on,emit,once,off,這種東西經常出面試題)
  • 第三方庫的事件系統

有事件,就有事件處理器:在事件處理器中,我們會應對這個事件做一些特殊操作。

那么瀏覽器怎么知道有事件發生了呢?怎么知道用戶對某個button做了一次click呢?

如果我們的主線程只是靜態的,沒有循環的話,可以用js偽代碼將其表述為:

function mainThread() {
     console.log("Hello World!");
     console.log("Hello JavaScript!");
}
mainThread();

執行完一次mainThread()之后,這段代碼就無效了,mainThread并不是一種激活狀態,對于I/O事件是沒有辦法捕獲到的。

因此對事件加入了“循環”,將渲染進程的主線程變為激活狀態,可以用js偽代碼表述如下:

// click event
function clickTrigger() {
    return "我點擊按鈕了"
}
// 可以是while循環
function mainThread(){
    while(true){
        if(clickTrigger()) { console.log(“通知click事件監聽器”) }
        clickTrigger = null;
     }
}
mainThread();

也可以是for循環

for(;;){
    if(clickTrigger()) { console.log(“通知click事件監聽器”) }
    clickTrigger = null;
}

在事件監聽器中做出響應:

button.addEventListener('click', ()=>{
    console.log("多虧了事件循環,我(瀏覽器)才能知道用戶做了什么操作");
})

什么是消息隊列?

消息隊列可以拆為“消息”+“隊列”。消息可以理解為用戶I/O;隊列就是先進先出的數據結構。而消息隊列,則是用于連接用戶I/O與事件循環的橋梁。

隊列數據結構圖

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

入隊出隊圖

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

在js中,如何發現出隊列FIFO的特性?

下面這個結構大家都熟悉,瞬間體現出隊列FIFO的特性。

// 定義一個隊列
let queue = [1,2,3];
// 入隊
queue.push(4); // queue[1,2,3,4]
// 出隊
queue.shift(); // 1 queue [2,3,4]

假設用戶做出了"click button1","click button3","click button 2"的操作。事件隊列定義為:

const taskQueue = ["click button1","click button3","click button 2"];
while(taskQueue.length>0){
    taskQueue.shift(); // 任務依次出隊
}

任務依次出隊:"click button1""click button3""click button 2"

此時由于mainThread有事件循環,它會被瀏覽器渲染進程的主線程事件循環系統捕獲,并在對應的事件處理器做出響應。

button1.addEventListener('click', ()=>{
    console.log("click button1");
})
button2.addEventListener('click', ()=>{
    console.log("click button 2");
})
button3.addEventListener('click', ()=>{
   console.log("click button3")
})

依次打印:"click button1","click button3","click button 2"。

因此,可以將消息隊列理解為連接用戶I/O操作和瀏覽器事件循環系統的任務隊列

如何實現一個 EventEmitter(支持 on,once,off,emit)?

/**
 * 說明:簡單實現一個事件訂閱機制,具有監聽on和觸發emit方法
 * 示例:
 * on(event, func){ ... }
 * emit(event, ...args){ ... }
 * once(event, func){ ... }
 * off(event, func){ ... }
 * const event = new EventEmitter();
 * event.on('someEvent', (...args) => {
 *     console.log('some_event triggered', ...args);
 * });
 * event.emit('someEvent', 'abc', '123');
 * event.once('someEvent', (...args) => {
 *     console.log('some_event triggered', ...args);
 * });
 * event.off('someEvent', callbackPointer); // callbackPointer為回調指針,不能是匿名函數
 */
class EventEmitter {
  constructor() {
    this.listeners = [];
  }
  on(event, func) {
    const callback = (listener) => listener.name === event;
    const idx = this.listeners.findIndex(callback);
    if (idx === -1) {
      this.listeners.push({
        name: event,
        callbacks: [func],
      });
    } else {
      this.listeners[idx].callbacks.push(func);
    }
  }
  emit(event, ...args) {
    if (this.listeners.length === 0) return;
    const callback = (listener) => listener.name === event;
    const idx = this.listeners.findIndex(callback);
    if (idx === -1) return;
    const listener = this.listeners[idx];

    if (listener.isOnce) {
      listener.callbacks[0](...args);
      this.listeners.splice(idx, 1);
    } else {
      listener.callbacks.forEach((cb) => {
        cb(...args);
      });
    }
  }
  once(event, func) {
    const callback = (listener) => listener.name === event;
    let idx = this.listeners.findIndex(callback);
    if (idx !== -1) return;
    this.listeners.push({
      name: event,
      callbacks: [func],
      isOnce: true,
    });
  }
  off(event, func) {
    if (this.listeners.length === 0) return;
    const callback = (listener) => listener.name === event;
    let idx = this.listeners.findIndex(callback);
    if (idx === -1) return;
    let callbacks = this.listeners[idx].callbacks;
    for (let i = 0; i < callbacks.length; i++) {
      if (callbacks[i] === func) {
        callbacks.splice(i, 1);
        break;
      }
    }
  }
}

// let event = new EventEmitter();
// let onceCallback = (...args) => {
//   console.log("once_event triggered", ...args);
// };
// let onceCallback1 = (...args) => {
//   console.log("once_event 1 triggered", ...args);
// };
// // once僅監聽一次
// event.once("onceEvent", onceCallback);
// event.once("onceEvent", onceCallback1);
// event.emit("onceEvent", "abc", "123");
// event.emit("onceEvent", "abc", "456");

// let onCallback = (...args) => {
//   console.log("on_event triggered", ...args);
// };
// let onCallback1 = (...args) => {
//   console.log("on_event 1 triggered", ...args);
// };
// event.on("onEvent", onCallback);
// event.on("onEvent", onCallback1);
// event.emit("onEvent", "abc", "123");
// // off銷毀指定回調
// event.off("onEvent", onCallback);
// event.emit("onEvent", "abc", "123");

宏任務和微任務

  • 哪些屬于宏任務?
  • 哪些屬于微任務?
  • 事件循環,消息隊列與宏任務、微任務之間的關系是什么?
  • 為任務添加和執行流程示意圖

哪些屬于宏任務?

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI渲染

哪些屬于微任務?

  • Promise
  • MutationObserver
  • process.nextTick
  • queueMicrotask

事件循環,消息隊列與宏任務、微任務之間的關系是什么?

  • 宏任務入隊消息隊列,可以將消息隊列理解為宏任務隊列
  • 每個宏任務內有一個微任務隊列,執行過程中微任務入隊當前宏任務的微任務隊列
  • 宏任務微任務隊列為空時才會執行下一個宏任務
  • 事件循環捕獲隊列出隊的宏任務和微任務并執行

事件循環會不斷地處理消息隊列出隊的任務,而宏任務指的就是入隊到消息隊列中的任務,每個宏任務都有一個微任務隊列,宏任務在執行過程中,如果此時產生微任務,那么會將產生的微任務入隊到當前的微任務隊列中,在當前宏任務的主要任務完成后,會依次出隊并執行微任務隊列中的任務,直到當前微任務隊列為空才會進行下一個宏任務。

為任務添加和執行流程示意圖

假設在執行解析HTML這個宏任務的過程中,產生了Promise和MutationObserver這兩個微任務。

// parse HTML···
Promise.resolve();
removeChild();

微任務隊列會如何表現呢?

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 


淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

圖片引自:極客時間的《瀏覽器工作原理與實踐》

過程可以拆為以下幾步:

  1. 主線程執行JS Promise.resolve(); removeChild();
  2. parseHTML宏任務暫停
  3. Promise和MutationObserver微任務入隊到parseHTML宏任務的微任務隊列
  4. 微任務1 Promise.resolve()執行
  5. 微任務2 removeChild();執行
  6. 微任務隊列為空,parseHTML宏任務繼續執行
  7. parseHTML宏任務完成,執行下一個宏任務

瀏覽器頁面循環系統原理圖

以下所有圖均來自極客時間《《瀏覽器工作原理與實踐》- 瀏覽器中的頁面循環系統》,可以幫助理解消息隊列,事件循環,宏任務和微任務。

  • 消息隊列和事件循環
  • setTimeout
  • XMLHttpRequest
  • 宏任務

消息隊列和事件循環

線程的一次執行

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

在線程中引入事件循環

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

渲染進程線程之間發送任務

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 


淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

線程模型:隊列 + 循環

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

跨進程發送消息

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

單個任務執行時間過久

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

setTimeout

長任務導致定時器被延后執行

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

循環嵌套調用 setTimeout

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

XMLHttpRequest

消息循環系統調用棧記錄

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

XMLHttpRequest 工作流程圖

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

HTTPS 混合內容警告

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

使用 XMLHttpRequest 混合資源失效

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

宏任務

宏任務延時無法保證

淺談瀏覽器架構、單線程js、事件循環、消息隊列、宏任務和微任務

 

如果文中有不對的地方,歡迎指正和交流~

分享到:
標簽:架構 瀏覽器
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定