Event Loop - 事件隊列

2020-12-10 紙鶴視界

Event Loop

定義:

event - 事件 loop - 循環,既然叫事件循環,那麼循環的點在哪?循環的是一個又一個的任務隊列,這些任務隊列由宏任務和微任務構成兩條原則

一次處理一個任務一個任務開始後直到完成,不會被其他任務中斷事件處理之間的關係

一次事件處理中,最多處理一個宏任務,但是會處理所有的微任務,任務開始後,會將內部所有的宏觀函數加到宏觀隊列等待,會將所有的圍觀函數加到微觀隊列等待,當前宏任務處理完畢後,開始逐個處理微任務,當微任務執行處理完成,會檢查是否需要更新ui,如果是則重新渲染ui。之後再次檢查宏觀任務隊列中的下一個宏觀任務,取出來並且執行

執行規則

宏任務包含:

整體script(也叫js主進程)settimeoutsetintervalrequestAnimationFrame微任務包含:

Promise.then catch finallyGenerator 函數async/awaitMutationObserver異步任務都有哪些

回調函數Promise(注意new promise裡面的屬於同步任務)asyncGenerator事件監聽發布/訂閱計時器requestAnimationFrameMutationObserver題外話

瀏覽器的渲染,不同瀏覽器處理是不同的,但是大部分瀏覽器選擇一般是一秒鐘60幀率(也就是60fps),這意味著瀏覽器會在16ms左右渲染一幀,所以我們單個任務和該任務的所有附屬微任務都應該在16ms內完成,以達到顯示平滑流暢。

怎麼證明js事件隊列存在,就拿簡單的setTimeout來說

console.time("settimeout");setTimeout(() => { console.log('settimeout執行') console.timeEnd("settimeout");}, 1000);for (let a = 0; a < 50000; a++) { console.log(a)}time會輸出多少呢?這種事件是怎麼觸發的?

代碼是在一秒鐘後才加入事件隊列,之後等待執行。怎麼證明,很簡單我們在拿出一個定時器

console.time("settimeout");console.time("settimeout2");setTimeout(() => { console.log('settimeout執行') console.timeEnd("settimeout");}, 1000);setTimeout(() => { console.log('settimeout2執行') console.timeEnd("settimeout2");}, 1);for (let a = 0; a < 50000; a++) { console.log(a)}明明後聲明的定時器2,卻先執行了,不知道有沒有發現,定時器1和定時器2執行時間,很接近!!!這很重要,為什麼會這樣,這就是今天所說的事件隊列,當1毫秒時候,定時器2被加入了事件隊列,當一秒鐘時候定時器1被加入事件隊列,然後for執行完後,隊列中下一個任務為定時器2,但是他只有一個console故而很快,所以拿出了定時器進行執行。再來看個額外例子,來鞏固下:

console.time("settimeout");console.time("settimeout2");setTimeout(() => { console.log('settimeout執行') console.timeEnd("settimeout");}, 1000);for (let a = 0; a < 50000; a++) { console.log(a)}setTimeout(() => { console.log('settimeout2執行') console.timeEnd("settimeout2");}, 1);枯燥無味的定義基本就這樣,我們來從實踐來做分析

從練習題來說

console.log(1)setTimeout(function () { console.log(2) setTimeout(function () { console.log(3) })})setTimeout(function () {console.log(4)})console.log(5)第一次運行之後

這時當前主進程隊列已經結束,開始檢測微任務隊列是否還有未完成的任務,發現微任務隊列已經空了所以,當前宏任務隊列結束,開始下一組宏任務

settimeout2任務完結,檢查當前微任務隊列為空,開始下一組宏任務

所以最終答案為:1,5,2,4,3

注意:setTimeout是以其觸發事件為寫入隊列時間。如果這麼說不理解的話 可以將上面代碼改為如下:console.log(1)setTimeout(function () { console.log(2) setTimeout(function () { console.log(3) })})setTimeout(function () { console.log(4)})console.log(5)這樣就會輸出1,5,2,3,4

tips: setTimeout寫的1000不等於他就是在上一次事件結束後的1000ms,而是以他聲明開始就進行計時的。不過這不是本篇文章的核心,我們不深究他的邏輯,繼續看第二個例子

console.log(1)setTimeout(() => { console.log('2') Promise.resolve(4).then((res) => console.log(res))}, 0);setTimeout(() => { console.log('3')}, 0);

主進程執行完畢,檢查微任務隊列為空,當前宏任務結束,開啟下一組宏任務

settimeout宏任務執行完畢,檢查宏任務隊列,拿出settimeout3的宏任務,將它拿出來執行。這個比較簡單咱們就不畫圖了

所以本題答案為1,2,4,3

let promise = new Promise(function(resolve, reject) { console.log('1'); resolve();});promise.then(function() { console.log('2');});console.log(3)

檢查宏任務隊列發現為空,所以本次代碼結束

答案為:1,3,2

console.log(1);setTimeout(function(){ console.log(2);}, 0);Promise.resolve().then(function(){ console.log(3);}).then(function(){ console.log(4);});

當前宏任務已經結束,查看宏任務隊列中發現還有settimeout沒有執行,將它取出來執行,輸出2.

所以本題答案為:1,3,4,2

setTimeout(()=>{ console.log('1');},0);var obj={ func:function () { setTimeout(function () { console.log('2') },0); return new Promise(function (resolve) { console.log('3'); resolve(); }) }};obj.func().then(function () { console.log('4')});console.log('5');

主線進程及其微觀進程執行完畢,會拿出下一組settimeout1執行,執行後會進行檢測微觀隊列,如果沒有則會繼續往下取出settimeout2執行。至此程序結束。

所以本題答案為:3,5,4,1,2

console.log(1)const p = new Promise(function(resolve, reject) { console.log(2) resolve()}).then(() => { console.log(3) throw(new Error('錯誤'))}).catch(() => { console.log(4)})console.log(5)setTimeout(() => { console.log(6)}, 0);console.log(7)p.then(() => { console.log(8) throw(new Error('錯誤2'))})

至此,當前宏任務結束。檢查宏任務隊列。取出下一組宏任務,settimeout6,並執行

所以答案為:1,2,5,7,3,4,8,Error,6

最後留兩道題給大家做學習用。

如果不能一眼看出。可以像我一樣畫一個圖。進行梳理。本文中為了代碼整齊,settimeout都是直接簡化為儘快執行。其實settimeout應該是在到達他聲明的時間時候,才進入宏觀隊列排隊的。

根據以下規則,你將不會在遇到事件隊列的問題

一次處理一個任務宏任務+當前所有微任務為一組同步直接執行、異步會加入事件隊列所有異步的加入隊列時間均以他們觸發時間為準console.log(1)const p = new Promise(function(resolve, reject) { console.log(2) setTimeout(() => { console.log(9) }, 0); resolve()}).then(() => { console.log(3) throw(new Error('錯誤'))}).catch(() => { console.log(4)})console.log(5)setTimeout(() => { console.log(6)}, 0);console.log(7)p.then(() => { console.log(8) throw(new Error('錯誤2'))})console.log(1)const p = new Promise(function(resolve, reject) { console.log(2) setTimeout(() => { console.log(9) }, 0); resolve()}).then(() => { console.log(3) throw(new Error('錯誤'))}).catch(() => { console.log(4)})console.log(5)setTimeout(() => { console.log(6)}, 0);console.log(7)p.then(() => { console.log(8) throw(new Error('錯誤2'))})requestAnimationFrame(function() { console.log(10)})宏觀微觀

宏觀微觀不是嵌套!!!!!即使代碼嵌套了在隊列中也不是嵌套的!!!!

怎麼利用這些,在代碼中優化自己的代碼,舉個例子來說

for (let i = 0 ; i<50000;i++) { const div = document.createElement('div') div.innerText = i document.body.appendChild(div)}function slice(startSplitNumber, total, sliceNumber, cb) { const oneNumber = total/sliceNumber const start = startSplitNumber * oneNumber const end = (startSplitNumber + 1) * oneNumber if (start >= total) return setTimeout(() => { for(let i = start; i < end;i++) { cb(i) } slice(startSplitNumber + 1, total, sliceNumber, cb) }, 0)}slice(0, 50000, 5000, function (current) { const div = document.createElement('div') div.innerText = current document.body.appendChild(div)})

相關焦點

  • Event Loop 解疑
    本文結合之前我翻譯的 《深入探討 Node.js 中的隊列》,可以讓大家對 Node.js 中 Event Loop 和 隊列 有更完整的認識。在了解什麼是 Event Loop(事件循環) 之前,我們先了解它要解決什麼問題。
  • 【第1790期】圖解Event Loop
    正文從這開始~~事件循環(Event Loop),是每個JS開發者都會接觸到的概念,但是剛接觸時可能會存在各種疑惑。我是一個視覺型學習者,所以打算通過gif動圖的可視化形式幫助大家理解它。首先我們來看看,什麼是事件循環,我們為什麼要了解它呢?
  • 從一道面試題談談對 EventLoop 的理解
    js 引擎執行異步代碼而不用等待,是因有為有任務隊列和事件輪詢。任務隊列:任務隊列是一個先進先出的隊列,它裡面存放著各種任務回調。事件輪詢:事件輪詢是指主線程重複從任務隊列中取任務、執行任務的過程。瀏覽器的多線程繪製頁面,解析 HTML、CSS,構建 DOM 樹,布局和繪製等與 JS 引擎線程互斥,也就是所謂的 JS 執行阻塞頁面更新負責準執行準備好待執行的事件,即定時器計數結束,或異步請求成功並正確返回的事件與 GUI 渲染線程互斥,執行時間過長將阻塞頁面的渲染多個事件加入任務隊列的時候需要排隊等待(JS 的單線程)負責執行異步的定時器類的事件,如 setTimeout、setInterval
  • Event Loop的規範和實現
    task主要包含:setTimeout、setInterval、setImmediate、I/O、UI交互事件microtask主要包含:Promise、process.nextTick、MutaionObserver整個最基本的Event Loop如圖所示
  • 從JavaScript的事件循環到Promise
    熟悉Javascript的人都知道,Javascript內部有一個事件隊列,所有的處理方法都在這個隊列中,Javascript主線程就是輪詢這個隊列處理,這個好處是使得CPU永遠是忙碌狀態。這個機制和Windows中的消息泵是一樣的,都是靠消息(事件)驅動,對於setTimeout和setInterval來說,當js線程執行到該代碼片段時,js主線程會根據所設定的時間,當設定的時間到期時,將設置的回調函數放到事件隊列中,然後js主線程繼續去執行下邊的代碼,當js線程執行完主線程上的代碼之後,會去循環執行事件隊列中的函數。
  • EventBus—事件總線
    _events = {}每當我們要監聽一個事件,就往vm.events裡添加一個鍵值對,事件的名稱作為鍵,一個空數組作為值。例如我們要監聽的事件名稱為event1,則vm.$on('event2', cb4);// 用$once方法監聽event2,回調函數為cb5vm.$once('event2', cb5);// 觸發event2事件,會執行cb4和cb5vm.$emit('event2');// 再次觸發event2事件,這裡只會執行cb4,不會執行cb5,cb5隻會執行一次vm.
  • Netty的EventLoop和線程模型
    Netty4 的 I/O 和事件處理由 I/O 操作觸發的事件將流經安裝了一或多個ChannelHandler 的 ChannelPipeline。傳播這些事件的方法調用可隨後被 ChannelHandler攔截並可按需處理事件。
  • 高級前端進階,你了解事件循環嗎?
    前言:無論在工作中,還是在面試題中,event loop(事件循環)都十分重要,瀏覽器與nodejs中事件循環略有差異,本文只討論瀏覽器中的事件循環,nodejs以後再單獨寫一篇。由於涉及到的點會比較多,可能顯得比較囉嗦,請大家選擇性觀看,如果。
  • 面試問到 Event Loop,這樣回答最完美
    當代碼執行到 setTimeout/setInterval時,實際上是 JS引擎線程通知 定時觸發器線程,間隔一個時間後,會觸發一個回調事件,而 定時觸發器線程在接收到這個消息後,會在等待的時間後,將回調事件放入到由 事件觸發線程所管理的 事件隊列中。
  • 近義詞組:事件 accident, incident, event, affair
    這裡incident更加泛指不愉快,不尋常的事件,大的小的,有意無意的。【without incident】 平安無事的 ---event, 名詞, 事件,活動(anything that happens, especially something important or unusual)-This year's Olympic Games will be the biggest
  • 事件總線(Event Bus)知多少
    改造後的釣魚事件處理邏輯如下:public class FishingEventHandler : IEventHandler<FishingEventData>{    public void HandleEvent(FishingEventData eventData)    {        eventData.FishingMan.FishCount++;
  • 遊戲開發之旅-JavaScript事件循環
    本節是第四講的第三十三小節,上一節我們為大家介紹了JavaScript內存管理,本節將為大家介紹JavaScript事件循環的原理。並發模型與事件循環(Concurrency model and the event loop)JavaScript有一個基於事件循環的並發模型,事件循環負責執行代碼、收集和處理事件以及執行隊列中的子任務。
  • Node.js 的 EventEmitter 事件處理詳解
    在事件驅動的編程中,事件(event) 是一個或多個動作的結果,這可能是用戶的操作或者傳感器的定時輸出等。我們可以把事件驅動程序看作是發布-訂閱模型,其中發布者觸發事件,訂閱者偵聽事件並採取相應的措施。
  • setTimeout和setImmediate到底誰先執行,本文讓你徹底理解Event Loop
    事件觸發線程定時器線程其實只是一個計時的作用,他並不會真正執行時間到了的回調,真正執行這個回調的還是JS主線程。所以當時間到了定時器線程會將這個回調事件給到事件觸發線程,然後事件觸發線程將它加到事件隊列裡面去。最終JS主線程從事件隊列取出這個回調執行。事件觸發線程不僅會將定時器事件放入任務隊列,其他滿足條件的事件也是他負責放進任務隊列。
  • JS事件對象Event詳解
    ,都會產生一個與之對應的事件對象 event ,其中包含了觸發事件的元素、鍵盤滑鼠的狀態、位置等等內容。event 對象代表事件的狀態,比如觸發事件的元素、鍵盤按鍵的狀態、滑鼠的位置、滑鼠按鍵的狀態等等;event 對象是一個隱式參數,並且只在事件發生的過程中才有效;event 對象根據觸發方式的不同會具有不同的屬性,也就是說某些屬性只對特定事件有效,但所有內容都是繼承自 Event 對象;event 對象在 IE 與 Chrome 等瀏覽器表現不盡相同
  • 搞懂事件的使用,詳細解讀Solidity事件Event
    什麼是事件Evnet?就是以EVM日誌基礎設備提供一個接口,當被事件調用時,出發參數存儲到日誌中,其與合約地址關聯,並記錄到區塊鏈中。關係就是:區塊鏈是打包交易區塊組成的鏈條,每一個交易會包含0到多個記錄,日誌代表智能合約所觸發事件。
  • Out of the loop?
    here are a few articles that might help those who are out of the loop.If you’re out of the loop, that is, you don’t know what’s going on.The loop refers to, I think, the social circle or circles we belong or don’t belong.