Vue手寫實現(xiàn)異步更新詳解
前言:
本文將詳細講解具體的更新過程,并手寫實現(xiàn)Vue
的異步更新邏輯相關代碼。
收集去重后的watcher進行更新
這里先回顧一下依賴收集的相關知識:
- 頁面首次掛載,會從
vm
實例上獲取data
中的值,從而調用屬性的get
方法來收集watcher
- 當
vm
實例上的屬性更新它的值時,會執(zhí)行收集到的watcher
的update
方法
看下之前完成的代碼:
class Watcher { // some code ... update () { // 直接執(zhí)行更新操作 this.get() } }
那么watcher
的update
到底應該如何被執(zhí)行呢?這就是本文的重點。
watcher
的更新操作主要分為如下倆步:
- 將
watcher
去重后放到隊列中 - 在異步任務中執(zhí)行存放的所有
watcher
的run
方法
代碼如下:
class Watcher { // some code update () { queueWatcher(this); } run () { this.get(); } } export default Watcher; let queue = []; let has = {}; // 使用對象來保存id,進行去重操作 let pending = false; // 如果異步隊列正在執(zhí)行,將不會再次執(zhí)行 function flushSchedulerQueue () { queue.forEach(watcher => { watcher.run(); if (watcher.options.render) { // 在更新之后執(zhí)行對應的回調: 這里是updated鉤子函數(shù) watcher.cb(); } }); // 執(zhí)行完成后清空隊列 queue = []; has = {}; pending = false; } function queueWatcher (watcher) { const id = watcher.id; if (!has[id]) { queue.push(watcher); has[id] = true; if (!pending) { pending = true; // 異步執(zhí)行watcher的更新方法 setTimeout(flushSchedulerQueue) } } }
此時已經(jīng)實現(xiàn)了視圖的異步更新,但是Vue
還為用戶提供而了$nextTick
方法,讓用戶可以在DOM
更新之后做些事情。即$nextTick
中的方法會在flushSchedulerQueue
執(zhí)行后才能執(zhí)行,下面就來看下$nextTick
和視圖更新之間的邏輯。
實現(xiàn)nextTick方法
在queueWatcher
中其實并不是直接調用setTimeout
來進行視圖更新的,而是會調用內部的nextTick
方法。為用戶提供的$nextTick
方法,也會調用nextTick
方法。
該方法實現(xiàn)如下:
let callbacks = []; let pending = false; function flushCallbacks () { callbacks.forEach(cb => cb()); callbacks = []; pending = false; } export function nextTick (cb) { callbacks.push(cb); if (!pending) { pending = true; timerFunc(); } }
nextTick
會接收一個回調函數(shù),并將回調函數(shù)放到callbacks
數(shù)組中,之后會通過timerFunc
來異步執(zhí)行callbacks
中的每一個函數(shù):
let timerFunc; if (Promise) { timerFunc = function () { return Promise.resolve().then(flushCallbacks); }; } else if (MutationObserver) { timerFunc = function () { const textNode = document.createTextNode('1'); const observer = new MutationObserver(() => { flushCallbacks(); observer.disconnect(); }); const observe = observer.observe(textNode, { characterData: true }); textNode.textContent = '2'; }; } else if (setImmediate) { timerFunc = function () { setImmediate(flushCallbacks); }; } else { timerFunc = function () { setTimeout(flushCallbacks); }; }
timerFunc
對異步API
進行了兼容處理,分別會先嘗試使用微任務Promise.then
、MutationObserver
、setImmediate
,如果這些API
瀏覽器都不支持的話,那么會使用宏任務setTimeout
。
在queueWatcher
里我們將flushSchedulerQueue
作為參數(shù)執(zhí)行nextTick
:
function queueWatcher (watcher) { const id = watcher.id; if (!has[id]) { queue.push(watcher); has[id] = true; if (!pending) { pending = true; nextTick(flushSchedulerQueue); } } }
在Vue
原型上,也要增加用戶可以通過實例來調用的$nextTick
方法,其內部調用nextTick
:
Vue.prototype.$nextTick = function (cb) { nextTick(cb); };
$nextTick
會將用戶傳入的回調函數(shù)也放到callbacks
中,通過異步API
來執(zhí)行。
測試demo詳解
上面已經(jīng)講解了視圖更新和$nextTick
的實現(xiàn)代碼,接下來寫一個demo
來實踐一下。
下面是實際開發(fā)中可能會用到的一段代碼:
<div id="app">{{name}}</div> <script> const vm = new Vue({ el: '#app', data () { return { name: 'zs' }; } }); vm.name = 'ls'; console.log('$el', vm.$el); vm.$nextTick(() => { console.log('next tick $el', vm.$el); }); </script>
其輸出結果如下:
在了解了$nextTick
的具體實現(xiàn)后,我們詳細分析下代碼的執(zhí)行流程:
- 在修改值之后,我們將要更新的
watcher
隊列放到了flushSchedulerQueue
函數(shù)中來執(zhí)行 - 而
nextTick
將flushSchedulerQueue
放到了callbacks
中,通過異步任務來執(zhí)行flushCallbacks
- 由于異步任務要等到主線程中的代碼執(zhí)行完畢后才會執(zhí)行,所以此時先打印
vm.$el
,視圖尚未更新 - 接下來會繼續(xù)執(zhí)行
vm.$nextTick
,將vm.$nextTick
中的回調函數(shù)也放到了callbacks
中,但是其位置在flushSchedulerQueue
后邊 - 主線程中的代碼執(zhí)行完畢,開始執(zhí)行異步任務
flushCallbacks
。首先執(zhí)行flushSchedulerQueue
更新DOM
,然后再執(zhí)行$nextTick
中的回調函數(shù),此時回調函數(shù)中可以獲取到最新的DOM
到此這篇關于Vue手寫實現(xiàn)異步更新詳解的文章就介紹到這了,更多相關Vue異步更新內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue輸入框中輸完后光標自動跳到下一個輸入框中的實現(xiàn)方法
最近在工作中遇到了些問題,總結下分享給同樣遇到這個問題的朋友,這篇文章主要給大家介紹了關于vue輸入框中輸完后光標自動跳到下一個輸入框中的實現(xiàn)方法,需要的朋友可以參考下2023-03-03解決vue elementUI 使用el-select 時 change事件的觸發(fā)問題
這篇文章主要介紹了解決vue elementUI 使用el-select 時 change事件的觸發(fā)問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11