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