面試官:什麼是 EventLoop.你:一臉蒙蔽.看完這篇文章就懂了

2021-02-20 人生代碼
面試官:什麼是 EventLoop。你:一臉蒙蔽。看完這篇文章就懂了

文章翻譯自:

https://javascript.info/event-loop

在這片文章,我們要帶著兩個問題去學習

事件循環

瀏覽器 js 以及 Nodejs 都是基於事件循環,了解事件循環對於代碼優化非常重要。在本章中,我們首先介紹有關事物如何工作的理論細節,然後介紹該知識的實際應用。

就是有一個無限循環機制:JavaScript 引擎等待任務,執行任務,然後休眠,等待更多任務。

引擎的一般算法

這是瀏覽頁面時看到的形式化信息。JavaScript 引擎大部分時間不執行任何操作,僅在腳本/處理程序/事件激活時運行。

任務示例<script src="...">加載外部腳本時,任務是執行它用戶移動滑鼠時,任務是調度 mousemove 事件並執行處理程序當計劃好的時間到了 setTimeout,任務是運行其回調。

設置任務-引擎處理它們-然後等待更多任務(在睡眠時消耗接近零的CPU)。

引擎繁忙時可能會發生任務,然後將其排入隊列。

任務形成一個隊列,即所謂的「宏任務隊列」(v8術語):

例如,當引擎正忙於執行 script,用戶可能會移動滑鼠 mousemove,這 setTimeout 可能是由於任務到期而導致的,等等,這些任務形成了一個隊列,如上圖所示。

隊列中的任務按「先到先得」的原則處理。引擎瀏覽器用完成後 script,它將處理 mousemove 事件,然後 setTimeout 處理程序,依此類推。

到目前為止,很簡單,對吧?

另外兩個細節:引擎執行任務時永遠不會進行渲染。任務是否花費很長時間都沒關係。僅在任務完成後才繪製對 DOM 的更改。如果一項任務花費的時間太長,瀏覽器將無法執行其他任務,例如處理用戶事件。因此,過了一會兒,它會發出「頁面無響應」之類的警報,建議終止整個頁面的任務。當存在大量複雜的計算或導致無限循環的編程錯誤時,就會發生這種情況。用例1:分割 CPU 任務

假設我們有一個需要 CPU 的任務。

例如,語法高亮(用於著色此頁面上的代碼示例)相當佔用 CPU 資源。為了突出顯示代碼,它執行分析,創建許多彩色元素,然後將它們添加到文檔中-花費大量時間編寫大量文本。

當引擎忙於語法高亮顯示時,它無法執行其他與 DOM 相關的工作,處理用戶事件等。它甚至可能導致瀏覽器「打ic」甚至「掛起」一小段時間,這是不可接受的。

通過將大任務分成多個部分,我們可以避免問題。突出顯示前100行,然後為後100行計劃 setTimeout(零延遲),依此類推。

為了證明這種方法,為簡單起見,而不是文本的高亮顯示,讓我們一個函數,計算從1到1000000000。

如果您運行下面的代碼,引擎將「掛起」一段時間。對於明顯可見的伺服器端JS,如果您正在瀏覽器中運行它,則嘗試單擊頁面上的其他按鈕–您會發現在計數結束之前不會處理其他事件。

let i = 0;

let start = Date.now();

function count() {

  // do a heavy job
  for (let j = 0; j < 1e9; j++) {
    i++;
  }

  alert("Done in " + (Date.now() - start) + 'ms');
}

count();

瀏覽器甚至可能顯示「腳本花費太長時間」的警告。

讓我們使用嵌套 setTimeout 調用拆分作業:

let i = 0;

let start = Date.now();

function count() {

  // do a piece of the heavy job (*)
  do {
    i++;
  } while (i % 1e6 != 0);

  if (i == 1e9) {
    alert("Done in " + (Date.now() - start) + 'ms');
  } else {
    setTimeout(count); // schedule the new call (**)
  }

}

count();

現在,瀏覽器界面在「計數」過程中可以正常使用。

一次運行 count 完成一部分工作,然後根據需要重新計劃自身:

第二次運行計數:i=1000001..2000000。

現在,如果 onclick 在引擎正在忙於執行第1部分時出現新的輔助任務(例如事件),則將其排隊,然後在第1部分完成時在下一部分之前執行。count 執行之間定期返回事件循環為 JavaScript 引擎提供了足夠的「空氣」來執行其他操作,以對其他用戶操作做出反應。

值得注意的是,兩種變體(無論是否分配工作)setTimeout在速度上都是可比的。總體計數時間沒有太大差異。

為了使它們更接近,讓我們進行改進。

我們將排程移至的開頭 count():

let i = 0;

let start = Date.now();

function count() {

  // move the scheduling to the beginning
  if (i < 1e9 - 1e6) {
    setTimeout(count); // schedule the new call
  }

  do {
    i++;
  } while (i % 1e6 != 0);

  if (i == 1e9) {
    alert("Done in " + (Date.now() - start) + 'ms');
  }

}

count();

現在,當我們開始 count() 發現需要做 count() 更多的事情時,我們會立即安排工作時間,然後再進行這項工作。

如果您運行它,很容易注意到它花費的時間大大減少。

為什麼?

這很簡單:您記得,許多嵌套 setTimeout 調用在瀏覽器中的最小延遲為4ms 。即使我們設置了0,它4ms(或者更多)。因此,我們計劃得越早–運行速度越快。

最後,我們將需要大量 CPU 的任務分成了幾個部分–現在它不會阻塞用戶界面。而且它的整體執行時間不會更長。

用例2:進度指示

為瀏覽器腳本分配繁重任務的另一個好處是,我們可以顯示進度指示。

如前所述,僅在當前運行的任務完成後才繪製對DOM的更改,而不管它花費多長時間。

一方面,這很棒,因為我們的函數可能創建許多元素,將它們一個接一個地添加到文檔中並更改其樣式-訪問者將看不到任何「中間」未完成的狀態。重要的是吧?

這是演示,在i功能完成之前不會顯示對的更改,因此我們將僅看到最後一個值:

<div id="progress"></div>

<script>

  function count() {
    for (let i = 0; i < 1e6; i++) {
      i++;
      progress.innerHTML = i;
    }
  }

  count();
</script>

…但是我們也可能希望在任務執行過程中顯示一些東西,例如進度條。

如果我們使用來將繁重的任務分成幾部分 setTimeout,那麼更改將被繪製在它們之間。

這看起來更漂亮:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // do a piece of the heavy job (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3 != 0);

    if (i < 1e7) {
      setTimeout(count);
    }

  }

  count();
</script>

現在,<div>顯示的是的增加值 i,這是一種進度條。

用例3:在事件發生後採取措施

在事件處理程序中,我們可能會決定推遲一些操作,直到事件冒泡並在所有級別上得到處理。我們可以通過將代碼包裝為零延遲來實現 setTimeout。

在分派自定義事件一章中,我們看到了一個示例:自定義事件 menu-open 是在中分派的 setTimeout ,因此它在完全處理「 click」事件之後發生。

menu.onclick = function() {
  // ...

  // create a custom event with the clicked menu item data
  let customEvent = new CustomEvent("menu-open", {
    bubbles: true
  });

  // dispatch the custom event asynchronously
  setTimeout(() => menu.dispatchEvent(customEvent));
};

宏任務和微任務

隨著宏任務,在本章中所描述的,有 microtasks,在章節中提到 Microtasks。

微任務僅來自我們的代碼。它們通常是由.then/catch/finallyPromise創建的:處理程序的執行成為微任務。微任務也被「秘密使用」 await,因為它是承諾處理的另一種形式。

還有一個特殊功能queueMicrotask(func),func 可以在微任務隊列中排隊等待執行。

每一個後立即宏任務時,引擎執行所有任務 microtask 隊列運行任何其他宏任務或渲染或其他任何東西之前,。

例如,看一下:

setTimeout(() => alert("timeout"));

Promise.resolve()
  .then(() => alert("promise"));

alert("code");

這將是什麼順序?

promise顯示第二個,因為它.then通過微任務隊列,並在當前代碼之後運行。

更豐富的事件循環圖片如下所示(順序是從上到下,即:首先是腳本,然後是微任務,渲染,等等):

在執行任何其他事件處理或呈現或執行任何其他宏任務之前,所有微任務都已完成。

這很重要,因為它可以確保微任務之間的應用程式環境基本相同(沒有滑鼠坐標更改,沒有新的網絡數據等)。

如果我們想異步執行一個函數(在當前代碼之後),但是在呈現更改或處理新事件之前,可以使用進行調度queueMicrotask。

這是一個帶有「計數進度條」的示例,與之前顯示的示例相似,但queueMicrotask用於代替setTimeout。您可以看到它在最後渲染。就像同步代碼一樣:

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // do a piece of the heavy job (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3 != 0);

    if (i < 1e6) {
      queueMicrotask(count);
    }

  }

  count();
</script>

概要

更詳細的事件循環算法(儘管與規範相比仍簡化了):

1從宏任務隊列中出隊並運行最早的任務(例如「腳本」)。2執行所有微任務:- 當微任務隊列不為空時:- 出隊並運行最舊的微任務。

要安排新的宏任務:

這可用於將繁重的計算任務分解為多個部分,以使瀏覽器能夠對用戶事件做出反應並顯示它們之間的進度。

另外,在事件處理程序中用於安排事件完全處理(冒泡完成)後的操作。

安排新的微任務

微任務之間沒有 UI 或網絡事件處理:它們立即接連運行。

因此,您可能想queueMicrotask 異步執行功能,但要在環境狀態下執行。

相關焦點

  • 一篇文章教會你 Event loop——瀏覽器和 Node
    但是發現整個Event loop儘管有很多篇文章,但是沒有一篇可以看完就對它所有內容都了解的文章。大部分的文章都只闡述了瀏覽器或者Node二者之一,沒有對比的去看的話,認識總是淺一點。所以才有了這篇整理了百家之長的文章。1. 定義Event loop:即事件循環,是JavaScript引擎處理異步任務的方式。
  • 如何解釋Event Loop面試官才滿意?
    就像還不能自助點餐的時候你去肯德基需要排隊,有的人沒想好點什麼或者點的東西很多,耗時就會長,那麼後面的人也只好排隊等待。有了自助點餐服務後,一切問題迎刃而解。同步任務就是在主線程上排隊執行的任務,只能執行完一個再執行下一個。異步任務則不進入主線程,而是先在event table中註冊函數,當滿足觸發條件後,才可以進入任務隊列來執行。只有任務隊列通知主線程說,我這邊異步任務可以執行了,這個時候此任務才會進入主線程執行。
  • 你不知道的 Event Loop
    筆者最近忙著做項目之類的,文章輸出遺落下了一段時間,這次我們就來聊一個面試中一個比較重要的知識點 —— Event Loop可能有人會奇怪一個 EventLoop 還能寫出什麼,且聽我慢慢來逼叨,看完這篇文章帶你搞定 Event Loop 以及它相關的一些知識點。
  • 面試官:什麼是CSRF?(你的錢為什麼被轉走,這篇文章告訴你答案)
    這段時間很多文章標題都是面試官,所以跟個風,這篇文章也以面試官開頭,主要內容是關於CSRF。
  • Event Loop淺談
    event loop 即事件循環。最初了解到js的event loop機制是通過自己對js中異步、同步的疑惑。
  • 一次弄懂Event Loop
    應對各大網際網路公司的面試,懂其原理,題目任其發揮。pending callback: 上一輪循環中少數的callback會放在這一階段執行。idle, prepare: 僅在內部使用。poll: 最重要的階段,執行pending callback,在適當的情況下會阻塞在這個階段。
  • 深入理解 Event Loop
    點擊閱讀原文查看 IMWeb 社區更多精彩文章。眾所周知,javascript 是單線程的,其通過使用異步而不阻塞主進程執行。那麼,他是如何實現的呢?本文就瀏覽器與nodejs環境下異步實現與event loop進行相關解釋。瀏覽器環境瀏覽器環境下,會維護一個任務隊列,當異步任務到達的時候加入隊列,等待事件循環到合適的時機執行。
  • EventLoop面試必考,你完全會了麼?
    需要注意的是這個方法雖然能夠保證回調函數在每一幀內只渲染一次,但是如果這一幀有太多任務執行,還是會造成卡頓的;因此它只能保證重新渲染的時間間隔最短是屏幕的刷新時間。具體還是可以看MDN:requestIdleCallback事件循環,宏任務,微任務的關係遇到promise.then等微任務的時候會放進微任務隊列尾部遇到setTimeout,setInterval 等宏任務的時候會放入宏任務的隊列尾部最大的宏任務執行完後,查看有沒有微任務,有的話執行微任務,沒有的話執行下一個宏任務執行setTimeout
  • 【小心得】淺析Nodejs Event Loop
    每一個階段都有一個裝有callbacks的fifo queue(隊列),當event loop運行到一個指定階段時,node將執行該階段的fifo queue(隊列),當隊列callback執行完或者執行callbacks數量超過該階段的上限時,event loop會轉入下一下階段.
  • 路由器:什麼是軟路由,看完本篇文章你就懂了!
    很多軟路由支持單線多撥,即單個帳號可以撥多次,通常情況下允許撥4次左右,每個運營商的限制不一樣,如下圖所示,一般使用軟路由器撥號兩次,實現了寬帶的多線負載,可以很大程度上提高多線程下載的網速。軟路由的安裝相比較硬路由來說,安裝成本是比較高的,安裝軟路由需要有一定硬/軟體知識(硬體方面知道什麼是CPU、什麼是內存。軟體方面最最最起碼要知道什麼是PE,會重裝作業系統)等。1、軟路由器硬體準備軟路由一般是由硬體加路由程序組成。
  • 只有程式設計師看的懂的面試聖經|如何拿下編程面試
    現在回頭看那個時候,我發現自己當時去參加面試都完全沒做任何準備。雖然已經有許多博客文章和書籍在講編程面試,但現在的我作為面試官,坐在桌子的另一邊,還是能看到許多來參加編程面試的人沒做任何準備,或者準備得很糟糕。這也就是為什麼我開始寫這篇指南的原因,剛畢業時的我、第一次參加面試的我一定非常想有這麼一份指南來指引自己。而從現在開始,我自己也會照著這份指南去做。
  • 看完這篇文章,你不可能不懂「動態代理」
    "你這種想法是很多初學者的通病,學一個知識點的時候總是不自覺地把其他相關知識點也學了一遍,最後忘了自己一開始的學習目的是什麼,本末倒置。記住,要先掌握脈絡,再學細節!"陀螺正色道。「還有第5步,我也不是很懂。」招財繼續追問。「別急,先看一下我們目前為止的所有代碼,然後解釋給你聽。」
  • 聽我講完 redo log、binlog 原理,面試官老臉一紅!
    面試官:額…你好,小李啊,看你簡歷寫著精通MySQL事務、日誌原理,你認為精通應該是啥水平呢?熊貓(老臉一紅):emmm…精通就是,比面試官知道的多一點唄。。面試官:小夥子你這思路很奇特呀!那你再詳細跟我說一下,啥是WAL技術?熊貓:(小馬哥對我有意思啊!)
  • 看完這篇,再也不怕面試問樹了!
    我們今天先來看,什麼是「樹」。樹是由頂點和邊組成的且不存在環的數據結構。作為一個應用非常廣的數據結構,不僅在工作中常用,在面試中也非常常考。一是因為樹的結構天然決定了它和遞歸聯繫緊密,很多樹相關的算法題都非常適合用遞歸來解;二是因為它的難度介於鍊表和圖之間,非常適合在 45 分鐘的面試裡進行考察,所以一場面試中遇到兩三輪問樹都是再正常不過的了。
  • 騰訊面試官:如何停止一個正在運行的線程?我一臉蒙蔽...
    由於公眾號文章推送規則的改變,為了能準時收到我們的文章推送,請將公眾號: 程式設計師大廠面試 設為星標~這樣就不會錯過每一篇精彩的推送啦
  • Event Loop的規範和實現
    一直以來,我對Event Loop的認知界定都是可知可不知的分級,因此僅僅保留淺顯的概念,從未真正學習過,直到看了這篇文章
  • 【每日一題】(29題)面試官:HTML-HTML5新增標籤屬性的理解?
    一、前言2020.12.23 立的 flag,每日一題,題目類型不限制,涉及到JavaScript,Node,Vue,React,瀏覽器,http,算法,HTML5等領域。本文是:【每日一題】(29題)面試官:HTML-HTML5新增標籤屬性的理解?
  • 不懂後臺伺服器學什麼的快點看過來
    後臺伺服器開發精選博客匯總一、前言相信很多朋友,很多志在學習後臺伺服器開發,想在這一鄰域有一些作為,但是經常苦苦不知道學習什麼,我也經常想要收集一些博客分享給大家,但是苦苦時間不是那麼充足,不過還好,有很多優秀的博主為們準備好了,在這裡,感謝這些優秀的博主;這篇文章來自於CSDN社區
  • jsliang 求職系列 - 06 - Event Loop
    一 目錄 不折騰的前端,和鹹魚有什麼區別目錄一 目錄二 前言三 單線程和多線程四 Event Loop 4.1 Event Loop 執行過程 4.2 requestAnimationFrame  4.2.1 requestAnimationFrame 介紹  4.2.2 requestAnimationFrame
  • 看完這篇 Session、Cookie、Token,和面試官扯皮就沒問題了
    HTTP 協議中的 Cookie 包括 Web Cookie 和瀏覽器 Cookie,它是伺服器發送到 Web 瀏覽器的一小塊數據。如果沒有這兩者,那你可能需要在每個頁面切換時都需要進行登錄了。因為 HTTP 是一個無狀態的協議。這也就意味著當你訪問某個網頁,然後單擊同一站點上的另一個頁面時,伺服器的內存中將不會記住你之前的操作。因此,如果你登錄並訪問了你有權訪問的另一個頁面,由於 HTTP 不會記錄你剛剛登錄的信息,因此你將再次登錄。