1. 程式人生 > >Event Loop - 事件佇列

Event Loop - 事件佇列

# Event Loop ### 定義: event - 事件 loop - 迴圈,既然叫事件迴圈,那麼迴圈的點在哪? 迴圈的是一個又一個的任務佇列,這些任務佇列由巨集任務和微任務構成 ### 兩條原則 1. 一次處理一個任務 2. 一個任務開始後直到完成,不會被其他任務中斷 ### 事件處理之間的關係 一次事件處理中,最多處理一個巨集任務,但是會處理所有的微任務,任務開始後,會將內部所有的巨集觀函式加到巨集觀佇列等待,會將所有的圍觀函式加到微觀佇列等待,當前巨集任務處理完畢後,開始逐個處理微任務,當微任務執行處理完成,會檢查是否需要更新ui,如果是則重新渲染ui。之後再次檢查巨集觀任務佇列中的下一個巨集觀任務,取出來並且執行 ### 執行規則 ![image](https://i.loli.net/2020/11/15/6shiJrN4FGKUYDx.png) ### 巨集任務包含: 1. 整體script(也叫js主程序) 2. settimeout 3. setinterval 4. requestAnimationFrame ### 微任務包含: 1. Promise.then catch finally 2. Generator 函式 3. async/await 4. MutationObserver ### 非同步任務都有哪些 1. 回撥函式 2. Promise(注意new promise裡面的屬於同步任務) 3. async 4. Generator 5. 事件監聽 6. 釋出/訂閱 7. 計時器 8. requestAnimationFrame 9. MutationObserver ### 題外話 瀏覽器的渲染,不同瀏覽器處理是不同的,但是大部分瀏覽器選擇一般是一秒鐘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) ``` 第一次執行之後 ![image.png](https://i.loli.net/2020/12/02/bnvPfgGCHO7Dh2l.png) 這時當前主程序佇列已經結束,開始檢測微任務佇列是否還有未完成的任務,發現微任務佇列已經空了所以,當前巨集任務佇列結束,開始下一組巨集任務 ![image.png](https://i.loli.net/2020/12/02/L4wb2TQDajuhgI3.png) settimeout2任務完結,檢查當前微任務佇列為空,開始下一組巨集任務 ![image.png](https://i.loli.net/2020/12/02/OUYfHJsbqItMdXh.png) 所以最終答案為: 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); ``` ![image.png](https://i.loli.net/2020/12/02/NQ5Z2IV4wJ9tGv7.png) 主程序執行完畢,檢查微任務佇列為空,當前巨集任務結束,開啟下一組巨集任務 ![image.png](https://i.loli.net/2020/12/02/PeZ5qoLAMhENgY6.png) 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) ``` ![image.png](https://i.loli.net/2020/12/02/1VHNpoTmrJUF8xb.png) 檢查巨集任務佇列發現為空,所以本次程式碼結束 答案為:1,3,2 ``` console.log(1); setTimeout(function(){ console.log(2); }, 0); Promise.resolve().then(function(){ console.log(3); }).then(function(){ console.log(4); }); ``` ![image.png](https://i.loli.net/2020/12/02/JyxUoT69kzKrDmg.png) 當前巨集任務已經結束,檢視巨集任務佇列中發現還有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'); ``` ![image.png](https://i.loli.net/2020/12/02/cRJszIGyMUg7at8.png) 主線程序及其微觀程序執行完畢,會拿出下一組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')) }) ``` ![image.png](https://i.loli.net/2020/12/02/aeUfDEZdjhFqC6b.png) 至此,當前巨集任務結束。檢查巨集任務佇列。取出下一組巨集任務,settimeout6,並執行 所以答案為:1,2,5,7,3,4,8,Error,6 最後留兩道題給大家做學習用。 如果不能一眼看出。可以像我一樣畫一個圖。進行梳理。本文中為了程式碼整齊,settimeout都是直接簡化為儘快執行。其實settimeout應該是在到達他宣告的時間時候,才進入巨集觀佇列排隊的。 根據以下規則,你將不會在遇到事件佇列的問題 1. 一次處理一個任務 2. 巨集任務+當前所有微任務為一組 3. 同步直接執行、非同步會加入事件佇列 4. 所有非同步的加入佇列時間均以他們觸發時間為準 ``` 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