Vue.js原理分析之nextTick實現(xiàn)詳解
前言
tips:第一次發(fā)技術文章,篇幅比較簡短,主要采取文字和關鍵代碼表現(xiàn)的形式,希望幫助到大家。(若有不正確還請多多指正)
nextTick作用和用法
用法:nextTick接收一個回調函數作為參數,它的作用是將回調延遲到下一次DOM更新之后執(zhí)行,如果沒有提供回調函數參數且在支持Promise的環(huán)境中,nextTick將返回一個Promise。
適用場景:開發(fā)過程中,開發(fā)者需要在更新完數據之后,需要對新DOM做一些操作,其實我們當時無法對新DOM進行操作,因為這時候還沒有重新渲染,這時候nextTick就派上了用場。
nextTick實現(xiàn)原理
下面我們介紹下nextTick工作原理:
首先我們應該了解到更新完數據(狀態(tài))之后,DOM更新這個動作并不是同步進行的,而是異步的。Vue.js中有一個隊列,每當需要渲染時,會將Watcher推送到這個隊列中,等下一次事件循環(huán)中再讓Watcher觸發(fā)渲染流程。這里我們可能會有兩個疑問:
**1.為什么更新DOM是異步的?**
我們知道從Vue2.0開始使用虛擬DOM進行渲染,變化偵測只發(fā)送到組件級別,組件內部則通過虛擬DOM的diff(比對)而進行局部渲染,而在同一次事件循環(huán)中組件假如收到兩份通知,組件是否會進行兩次渲染呢?事實上一次事件循環(huán)組件會在所有狀態(tài)修改完畢之后只進行一次渲染操作。
**2.什么是事件循環(huán)?**
javascript是單線程腳本語言,它具有非阻塞特性,之所以非阻塞是由于在處理異步代碼時,主線程會掛起這個任務,當異步任務處理完畢之后會根據一定的規(guī)則去執(zhí)行異步任務的回調,異步任務分宏任務(macrotast)和微任務(microtast),它們會被分配到不同的隊列中,當執(zhí)行棧所有任務執(zhí)行完畢之后,會先檢查微任務隊列中是否有事件存在,優(yōu)先執(zhí)行微任務隊列事件對應的回調,直至為空。然后再執(zhí)行宏任務隊列中事件的回調。無限重復這個過程,形成一個無限循環(huán)就叫做事件循環(huán)。
常見微任務包括:Promise 、MutationObserver、Object.observer、process.nextTick等
常見宏任務包括:setTimeout、setInterval、setImmediate、MessageChannel、requestAnimation、UI交互事件等
微任務如何注冊?
nextTick會將回調添加到異步任務隊列中延遲執(zhí)行,在執(zhí)行回調前,反復調用nextTick,Vue并不會反復添加到任務隊列中,只會向任務隊列添加一個任務,多次使用nextTick只會將回調添加到回調列表緩存起來,當任務觸發(fā)時,會清空回調列表并依次執(zhí)行所有回調 ,具體代碼如下:
const callbacks = [] let pending = false function flushCallbacks(){ //執(zhí)行回調 pending = false const copies = callbacks.slice(0) callbacks.length = 0 //清空回調隊列 for(let i = 0; i < copies.length; i++) { copies[i]() } } let microTimerFunc const p = Promise.resolve() microTimerFunc = () => { //注冊微任務 p.then(flushCallbacks) } export function nextTick(cb,ctx){ callbacks.push(()=>{ if(cb){ cb.call(ctx) } }) if(!pending){ pending = true //將pending設置為true,保證任務在依次事件循環(huán)中不會重復添加 microTimerFunc() } }
由于微任務優(yōu)先級太高,可能在某些場景下需要使用到宏任務,所以Vue提供了可以強制使用宏任務的方法withMacroTask。具體實現(xiàn)如下:
const callbacks = [] let pending = false function flushCallbacks(){ //執(zhí)行回調 pending = false const copies = callbacks.slice(0) callbacks.length = 0 //清空回調隊列 for(let i = 0; i < copies.length; i++) { copies[i]() } } let microTimerFunc //新增代碼 let macroTimerFunc = function(){ ... } let useMacroTask = false const p = Promise.resolve() microTimerFunc = () => { //注冊微任務 p.then(flushCallbacks) } //新增代碼 export function withMacroTask(fn){ return fn._withTask || fn._withTask = function()=>{ useMacroTask = true const res = fn.apply(null,arguments) useMacroTask = false return res } } export function nextTick(cb,ctx){ callbacks.push(()=>{ if(cb){ cb.call(ctx) } }) if(!pending){ pending = true //將pending設置為true,保證任務在依次事件循環(huán)中不會重復添加 //修改代碼 if(useMacroTask){ macroTimerFunc() }else{ microTimerFunc() } } }
上面提供了一個withMacroTask方法強制使用宏任務,通過useMacroTask變量進行控制是否使用注冊宏任務執(zhí)行,withMacroTask實現(xiàn)很簡單,先將useMacroTask變量設置為true,然后執(zhí)行回調,回調執(zhí)行之后再改回false。
宏任務是如何注冊?
注冊宏任務優(yōu)先使用setImmediate,但是存在兼容性問題,只能在IE中使用,所以使用MessageChannel作為備選方案,若以上都不支持則最后會使用setTimeout。具體實現(xiàn)如下:
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){ macroTimerFunc = ()=>{ setImmediate(flushCallbacks) } } else if( typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]') ){ const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = ()=>{ port.postMessage(1) } } else { macroTimerFunc = ()=>{ setTimout(flushCallbacks,0) } }
microTimerFunc的實現(xiàn)方法是通過Promise.then,但是并不是所有瀏覽器都支持Promise,當不支持的時候采取降級為宏任務方式
if(typeof Promise !== 'undefined' && isNative(Promise)){ const p = Promise.resolve() microTimerFunc = ()=>{ p.then(flushCallbacks) } } else { microTimerFunc = macroTimerFunc }
若未提供回調且環(huán)境支持Promise情況下,nextTick會返回一個Promise,具體實現(xiàn)如下:
export function nextTick(cb, ctx) { let _resolve callbacks.push(()=>{ if(cb){ cb.call(ctx) }else{ _resolve(ctx) } }) if(!pending){ pending = true if(useMacroTask){ macroTimerFunc() }else{ microTimerFunc() } } if(typeof Promise !== 'undefined' && isNative(Promise)){ return new Promise(resolve=>{ _resolve = resolve }) } }
以上是nextTick運行原理的設計,完整代碼如下:
const callbacks = [] let pending = false function flushCallbacks(){ //執(zhí)行回調 pending = false const copies = callbacks.slice(0) callbacks.length = 0 //清空回調隊列 for(let i = 0; i < copies.length; i++) { copies[i]() } } let microTimerFunc let macroTimerFunc let useMacroTask = false //注冊宏任務 if(typeof setImmediate !== 'undefined' && isNative(setImmediate)){ macroTimerFunc = ()=>{ setImmediate(flushCallbacks) } } else if( typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]') ){ const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = ()=>{ port.postMessage(1) } } else { macroTimerFunc = ()=>{ setTimout(flushCallbacks,0) } } //微任務注冊 if(typeof Promise !== 'undefined' && isNative(Promise)){ const p = Promise.resolve() microTimerFunc = ()=>{ p.then(flushCallbacks) } } else {//降級處理 microTimerFunc = macroTimerFunc } export function withMacroTask(fn){ return fn._withTask || fn._withTask = function()=>{ useMacroTask = true const res = fn.apply(null,arguments) useMacroTask = false return res } } export function nextTick(cb,ctx){ let _resolve callbacks.push(()=>{ if(cb){ cb.call(ctx) }else{ _resolve(ctx) } }) if(!pending){ pending = true //將pending設置為true,保證任務在依次事件循環(huán)中不會重復添加 //修改代碼 if(useMacroTask){ macroTimerFunc() }else{ microTimerFunc() } } if(typeof Promise !== 'undefined' && isNative(Promise)){ return new Promise(resolve=>{ _resolve = resolve }) } }
以上便是對nextTick的實現(xiàn)原理的全部介紹。
參考資料
Vue.js深入淺出
總結
到此這篇關于Vue.js原理分析之nextTick實現(xiàn)詳解的文章就介紹到這了,更多相關Vue.js原理之nextTick實現(xiàn)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- Vue nextTick的原理解析
- Vue中的nextTick作用和幾個簡單的使用場景
- 淺析vue中的nextTick
- 全面解析Vue中的$nextTick
- VUE異步更新DOM - 用$nextTick解決DOM視圖的問題
- Vue中this.$nextTick的作用及用法
- 詳解vue中$nextTick和$forceUpdate的用法
- 深入學習Vue nextTick的用法及原理
- vue中nextTick用法實例
- vue源碼nextTick使用及原理解析
- 一文了解Vue中的nextTick
- 瀏覽器事件循環(huán)與vue nextTicket的實現(xiàn)
- vue中$nextTick的用法講解
- 從源碼里了解vue中的nextTick的使用
- vue2.0$nextTick監(jiān)聽數據渲染完成之后的回調函數方法
- 詳解vue指令與$nextTick 操作DOM的不同之處
- 深入理解Vue nextTick 機制
- 談談Vue中的nextTick
相關文章
vue3的setup語法如何自定義v-model為公用hooks
這篇文章主要介紹了vue3的setup語法如何自定義v-model為公用hooks,文章分為兩個部分介紹,簡單介紹vue3的setup語法如何自定義v-model和如何提取v-model語法作為一個公用hooks2022-07-07vue3+ts+vite打包后靜態(tài)資源404無法加載js和css問題解決辦法
這篇文章主要給大家介紹了關于vue3+ts+vite打包后靜態(tài)資源404無法加載js和css問題的解決辦法,文中通過代碼以及圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-04-04