Vue源碼解讀系列篇javascript
相信你們都知道,Vue
能夠作到數據驅動視圖更新,好比咱們就簡單寫一個事件以下:html
methods: {
tap() {
for (let i = 0; i < 10; i++) {
this.a = i;
}
this.b = 666;
},
},
複製代碼
當咱們觸發這個事件,視圖中的a
和b
確定會發現一些變化。vue
那咱們思考一下,Vue
是如何管理這個變化的過程?好比上面這個案例,a
被循環了10次,那Vue
會去渲染視圖10次嗎?顯然不會,畢竟這個性能代價很是大。畢竟咱們只須要a
最後一次的賦值。java
實際上Vue
是異步更新視圖的,也就是說會等這個tap
事件執行完,檢查發現只須要更新a
和b
,而後再一次性更新,避免無效的更新。node
Vue
官方文檔也印證了咱們的想法,以下:react
Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。git
以上能夠詳見Vue官方文檔 - 異步更新隊列。github
Vue
通知視圖更新,是經過dep.notify
,相信你讀到這裏確定是瞭解Vue
響應式原理的。那麼來查看下dep.notify
都作了什麼?耐心點,離真相愈來愈近了。面試
// dep.js
notify () {
const subs = this.subs.slice();
// 循環通知全部watcher更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
複製代碼
首先循環通知全部watcher
更新,咱們發現watcher
執行了update
方法。瀏覽器
// watcher.js
update () {
if (this.lazy) {
// 若是是計算屬性
this.dirty = true
} else if (this.sync) {
// 若是要同步更新
this.run()
} else {
// 進入更新隊列
queueWatcher(this)
}
}
複製代碼
update
方法首先判斷是否是計算屬性或開發者定義了同步更新,這些咱們先不看,直接進入正題,進入異步隊列方法queueWatcher
。
那麼再來看下queueWatcher
,我省略了絕大部分代碼,畢竟代碼是枯燥的,爲了方便你們理解,都是一些思路性代碼。
export function queueWatcher (watcher: Watcher) {
// 獲取watcherid
const id = watcher.id
if (has[id] == null) {
// 保證只有一個watcher,避免重複
has[id] = true
// 推入等待執行的隊列
queue.push(watcher)
// ...省略細節代碼
}
// 將全部更新動做放入nextTick中,推入到異步隊列
nextTick(flushSchedulerQueue)
}
function flushSchedulerQueue () {
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.run()
// ...省略細節代碼
}
}
複製代碼
經過上述代碼能夠看出咱們將全部要更新的watcher
隊列放入了nextTick
中。 nextTick
的官方解讀爲:
在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。
這裏的描述其實限制了nextTick
的技能,實際上nextTick
就是一個異步方法,也許和你使用的setTimeout
沒有太大的區別。
那來看下nextTick
的源碼究竟作了什麼?
nextTick
源碼不多,翻來翻去沒幾行,可是我也不打算展開講,由於看代碼真的很枯燥。 下面的代碼只有幾行,其實你能夠選擇跳過看結論。
// timerFunc就是nextTick傳進來的回調等... 細節不展開
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
// 當原生 Promise 不可用時,timerFunc 使用原生 MutationObserver
// MutationObserver不要在乎它的功能,其實就是個能夠達到微任務效果的備胎
)) {
timerFunc = () => {
// 使用 MutationObserver
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// 若是原生 setImmediate 可用,timerFunc 使用原生 setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最後的倔強,timerFunc 使用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
複製代碼
總結就是Promise
> MutationObserver
> setImmediate
> setTimeout
。
果真和setTimeout
沒有太大的區別~
再總結一下優先級:microtask (jobs)
優先。
nextTick
源碼爲何要microtask
優先?再理解這個問題答案以前,咱們還要複習eventLoop
知識。
用2張圖帶你們簡單回憶一下,可是就不細講了,你們能夠自行查找資料。
setTimeout
、onClick
等等的一些操做,咱們會將他的執行結果放入隊列,此期間主線程不阻塞。event loop
在隊列裏面從頭開始取,在執行棧中執行 event loop
永遠不會斷。Event Loop
(事件循環機制)。microtask
和宏任務tasks
隊列microtask
都執行完畢,注意是全部的,他纔會從宏任務tasks
隊列中取事件。event loop
還會繼續循環,他會再去微任務microtask
執行全部的任務,而後再從宏任務tasks
隊列裏面取一個,如此反覆循環。簡單的回憶了eventLoop
、微任務、宏任務後,咱們還要再拋出一個結論。
咱們發現,原來在執行微任務以後還會執行渲染操做!!!(固然並非每次都會,但至少順序咱們是能夠確定的)。
event loop
中屢次修改同一dom
,只有最後一次會進行繪製。Update the rendering
)會在event loop
中的tasks
和microtasks
完成後進行,但並非每輪event loop
都會更新渲染,這取決因而否修改了dom
和瀏覽器以爲是否有必要在此時當即將新狀態呈現給用戶。若是在一幀的時間內(時間並不肯定,由於瀏覽器每秒的幀數總在波動,16.7ms只是估算並不許確)修改了多處dom
,瀏覽器可能將變更積攢起來,只進行一次繪製,這是合理的。event loop
都即時呈現變更,可使用requestAnimationFrame
。這裏我拋出結論,緣由和理論知識能夠看這篇文章 從event loop規範探究javaScript異步及瀏覽器更新渲染時機 ,這位大神寫的很好。
不知道你們有沒有猜出【nextTick
爲何要儘量的microtask
優先?】 這裏又盜了大神的圖,event loop
的大體循環過程:
假設如今執行到某個 task,咱們對批量的dom
進行異步修改,咱們將此任務插進tasks
,也就是用宏任務實現。
顯而易見,這種狀況下若是task
裏排隊的隊列比較多,同時遇到屢次的微任務隊列執行完。那頗有可能觸發屢次瀏覽器渲染,可是依舊沒有執行咱們真正的修改dom
任務。
這種狀況,不只會延遲視圖更新,帶來性能問題。還有可能致使視圖上一些詭異的問題。 所以,此任務插進microtasks
:
task
隊列若是有大量的任務等待執行時,將
dom
的變更做爲
microtasks
而不是宏任務(
task
)能更快的將變化呈現給用戶。
之因此講這篇文章,是由於在最近在讀Vue的源碼,我看的是2.6.10, 發現nextTick和2.5版本的實現不太同樣。你們能夠看下這位大佬的文章 Vue.js 升級踩坑小記
文章內容基本都是在其餘大佬的基礎上進行的理解,講錯的你們能夠批評指正~
文章有一些結論直接參考其餘文章,本身實在是懶得寫啦~~
侵權刪 ^^