首先,都2021了,你們對task
、mircotask
、task queue
這些概念都很清楚了。但你們可否回答如下的問題:javascript
任務隊列
是一個隊列嗎,task
是否會分優先級?什麼樣的task
優先級更高?requestAnimateFrame
在什麼階段執行?requestIdleCallback
呢?事件循環
與瀏覽器渲染
之間有什麼關係?ok,若是這些問題你都能回答上來,大佬請私信我您的微信,我們♂深刻交流一下♀😂;若是有不清楚,也別慌,這篇文章就是爲了把這些東西都搞明白。css
如下的結論所有都根據HTML5 規範,英文好的小夥伴能夠直接去看html
爲了協調事件、用戶交互、腳本、渲染、網絡等,UA
必須使用事件循環。每一個UA
都有一個關聯的事件循環,這是該UA獨有的。vue
這裏的UA指的是user Agnet,其包括了代碼運行所需的環境——ECMAScript執行上下文、執行對戰、執行線程等,可是執行線程並不屬於UA
,因此有可能出現一個線程協同調度着多個事件循環。java
這樣說其實就很複雜了,咱們能夠簡單認爲一個tab頁面對應一個事件循環便可。react
An event loop has one or more task queues. A task queue is a Set of tasks.git
Task queues are sets,not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.github
一個事件循環有一個或多個任務隊列,任務隊列是多個任務的Set
。web
這裏解決了咱們的第一個問題:任務隊列並非一個隊列,由於在事件循環執行流程的第一步,是從選中的任務隊列中拿出第一個能夠執行的任務,而不是出隊第一個任務。算法
For example, a user agent could have one task queue for mouse and key events to which the user interaction task source is associated, and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.
剛剛上面也提到了,一個任務循環會有多個任務隊列,每一個任務隊列中保存着不一樣類型的任務,好比:
瀏覽器會在按照任務順序的前提下,將四分之三的優先級分配給用戶交互事件,來保證響應的及時性,同時也不會把其他的任務餓死
。
也就是由於這個緣由,在Vue中產生了這個issue,這個問題的具體緣由其實就是由於當時Vue的nextTick
的實現使用的是postMessage
,它做爲一個task
優先級很低,當遇到瀏覽器的滾動時,事件循環優先選擇了的用戶交互的任務隊列
,致使postMessage
中的渲染函數遲遲沒有執行,視圖沒有更新,在以後的版本中,尤大把nextTick
的實現改成了Mutation Observer
和Promise.resolve
的方案。
從選中的任務隊列中取出一個宏任務執行
檢查微任務隊列,執行微任務,清空微任務隊列,在執行過程當中產生的微任務均在本次事件循環中執行完畢
檢查是否須要更新,這裏有個概念叫作Rendering opportunities
,用來判斷本次task
執行完畢後是否須要進行更新,也就是說並非完成一次task
就要進行視圖的更新,頗有多是在屢次事件循環後,才進行一次視圖更新。
一般來講視圖更新的間隔是固定的,對應着屏幕刷新的頻率60fps
,也就是說每次視圖更新的間隔是在16.7
ms,當頁面性能沒法維持在這個頻率的時候,瀏覽器會將頻率降低到30fps,以免丟幀。
當瀏覽器上下文不可見時,頻率甚至能夠降到4fps
。這裏的瀏覽器上下文指的就是頁面,不可見的狀況好比iframe
、當前tab
不在此頁面等狀況
當瀏覽器認定當前修改渲染,不會有任何的改變,而且map of animation frame callbacks
爲空,也就是沒有調用requestAnimationFrame
時,也會跳過渲染
最後還有一種狀況,就是當瀏覽器認爲因爲其餘緣由最好跳過更新渲染(主要是爲了合併定時器回調)。
This step enables the user agent to prevent the steps below from running for other reasons, for example, to ensure certain tasks are executed immediately after each other, with only microtask checkpoints interleaved (and without, e.g., animation frame callbacks interleaved). Concretely, a user agent might wish to coalesce timer callbacks together, with no intermediate rendering updates.
當出現上述的任意一種狀況是,跳過3.x
的所有步驟。
3.1 若是頁面出現了窗口大小改變,直接執行resize
方法
3.2 若是頁面出現了滾動,直接執行scroll
方法
3.3 執行requestAnimationFrame
的回調
3.4 執行Intersection Observer
的回調
3.5 從新繪製用戶界面
啓動空閒期算法,也就是執行requestIdleCallback
的回調。
對於resize
和scroll
來講,並不會等到從新繪製用戶界面這一步,這樣會有不少的延遲,在CSSOM VIEW中有提到,若是是resize
,瀏覽器會當即觸發resize
事件;scroll
相對複雜一些,瀏覽器會初始化一個pending scroll event targets
,當事件循環觸發了scroll
方法的時候,會將targets收集到的元素隊列依次觸發scroll
事件,固然是冒泡的,若是是元素的scroll
事件的話還會將document
的事件取消掉。
以上就是基本的事件循環的流程,你們能夠對看着圖,對照的流程慢慢梳理。但願你們看完以後能夠完美回答,本文一開始的問題。
其中其實還有不少的細節,可是太過繁瑣,我並無所有列出來,若是還想深刻了解的話,能夠去看上文提到的HTML5的規範
在Vue文檔中有這樣一段描述,當Vue修改數據後,並不會當即觸發watcher
的更新,而是將其推入一個異步的隊列中,這個隊列將watcher
進行排序,而且會異步執行這些watcher
,固然這其中就包含更新DOM。
那咱們思考一下,這個所謂的異步
應該處於哪一個階段呢?答案是,微任務。Vue 在內部對異步隊列嘗試使用原生的 Promise.then
、MutationObserver
和 setImmediate
,若是執行環境不支持,則會採用 setTimeout(fn, 0)
代替。這也就是nextTick
的實現。
<div id="example">{{message}}</div>
<script> var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改數據 vm.$el.textContent === 'new message' // false Vue.nextTick( function () { vm.$el.textContent === 'new message' // true } ) </script>
複製代碼
事件循環正在執行宏任務
vm.message = 'new message'
觸發setter
,將render Watcher
放入異步隊列,Vue內部調用了nextTick
將異步隊列放入微任務隊列
因爲DOM沒有更新因此,第一次判斷textContent
的值是123
,因此是false
用戶調用了nextTick
,將回調推入微任務隊列,這裏注意並非將函數推入了異步隊列,異步隊列中只有watcher
,且將隊列中的watcher
所有執行完,是在一個微任務中完成的。
Task
執行完畢,開始執行清空微任務隊列,第一個微任務就是將異步隊列中的watcher
更新一遍
watcher
所有更新,DOM更新,這裏注意,是DOM更新而不是視圖更新,由於是否更新視圖是瀏覽器來決定的。若是這裏有點蒙,請再看一遍事件循環的流程。
執行下一個微任務,因爲此時DOM已經更新,因此判斷結果爲true。