Vue2和Vue3的nextTick實(shí)現(xiàn)原理
一次弄懂 Vue2 和 Vue3 的 nextTick 實(shí)現(xiàn)原理
今天是 Wed Apr 26 2023 14:29:19 GMT+0800 (China Standard Time),我們來聊一下 Vue 的異步更新機(jī)制中的 nextTick。Vue 中的數(shù)據(jù)綁定和模板渲染都是異步的,那么如何在更新完成后執(zhí)行回調(diào)函數(shù)呢?這就需要用到 Vue 的 nextTick 方法了。
Vue2 中的 nextTick
在 Vue2 中,nextTick 的實(shí)現(xiàn)基于瀏覽器的異步任務(wù)隊(duì)列和微任務(wù)隊(duì)列。
異步任務(wù)隊(duì)列
在瀏覽器中,每個(gè)宏任務(wù)結(jié)束后會檢查微任務(wù)隊(duì)列,如果有任務(wù)則依次執(zhí)行。當(dāng)所有微任務(wù)執(zhí)行完成后,才會執(zhí)行下一個(gè)宏任務(wù)。因此可以通過將任務(wù)作為微任務(wù)添加到微任務(wù)隊(duì)列中,來確保任務(wù)在所有宏任務(wù)執(zhí)行完畢后立即執(zhí)行。
而使用 setTimeout 可以將任務(wù)添加到異步任務(wù)隊(duì)列中,在下一輪事件循環(huán)中執(zhí)行。
在 Vue2 中,如果沒有指定執(zhí)行環(huán)境,則會優(yōu)先使用 Promise.then / MutationObserver,否則使用 setTimeout。
javascript復(fù)制代碼 // src/core/util/next-tick.js /* istanbul ignore next */ const callbacks = [] let pending = false function flushCallbacks() { 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)) { // 使用 setImmediate macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if ( typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]') ) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { // 使用 setTimeout macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } if (typeof Promise !== 'undefined' && isNative(Promise)) { // 使用 Promise.then const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { // 使用 MutationObserver const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(1)) observer.observe(textNode, { characterData: true }) microTimerFunc = () => { textNode.data = String(1) } } export function nextTick(cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
宏任務(wù)和微任務(wù)
在 Vue2 中,可以通過設(shè)置 useMacroTask 來使 nextTick 方法使用宏任務(wù)或者微任務(wù)。
Vue2 中默認(rèn)使用微任務(wù),在沒有原生 Promise 和 MutationObserver 的情況下,才會改用 setTimeout。
javascript復(fù)制代碼 let microTimerFunc let macroTimerFunc let useMacroTask = false // 默認(rèn)使用微任務(wù) if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // 使用 setImmediate macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if ( typeof MessageChannel !== 'undefined' && (isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]') ) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { // 使用 setTimeout macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } if (typeof Promise !== 'undefined' && isNative(Promise)) { // 使用 Promise.then const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { // 使用 MutationObserver const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(1)) observer.observe(textNode, { characterData: true }) microTimerFunc = () => { textNode.data = String(1) } } export function nextTick(cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
總結(jié)
在 Vue2 中,nextTick 的實(shí)現(xiàn)原理基于瀏覽器的異步任務(wù)隊(duì)列和微任務(wù)隊(duì)列。Vue2 默認(rèn)使用微任務(wù),在沒有原生 Promise 和 MutationObserver 的情況下才會改用 setTimeout。
Vue3 中的 nextTick
在 Vue3 中,nextTick 的實(shí)現(xiàn)有了較大變化,主要是為了解決瀏覽器對 Promise 的缺陷和問題。
Promise 在瀏覽器中的問題
在瀏覽器中,Promise 有一個(gè)缺陷:如果 Promise 在當(dāng)前事件循環(huán)中被解決,那么在 then 回調(diào)函數(shù)之前添加的任務(wù)將不能在同一個(gè)任務(wù)中執(zhí)行。
例如:
javascript復(fù)制代碼 Promise.resolve().then(() => { console.log('Promise 1') }).then(() => { console.log('Promise 2') }) console.log('Hello')
輸出結(jié)果為:
復(fù)制代碼 Hello Promise 1 Promise 2
這是因?yàn)?Promise 雖然是微任務(wù),但是需要等到當(dāng)前宏任務(wù)結(jié)束才能執(zhí)行。
Vue3 中解決 Promise 缺陷的方法
在 Vue3 中,通過使用 MutationObserver 和 Promise.resolve().then() 來解決 Promise 在瀏覽器中的缺陷。具體實(shí)現(xiàn)如下:
javascript復(fù)制代碼 const queue: Array<Function> = [] let has: { [key: number]: boolean } = {} let flushing = false let index = 0 function resetSchedulerState() { queue.length = 0 has = {} flushing = false } function flushSchedulerQueue() { flushing = true let job while ((job = queue.shift())) { if (!has[job.id]) { has[job.id] = true job() } } resetSchedulerState() } let macroTimerFunc let microTimerFunc if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushSchedulerQueue) } } else { macroTimerFunc = () => { setTimeout(flushSchedulerQueue, 0) } } if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushSchedulerQueue) if (isIOS) setTimeout(noop) } } else { microTimerFunc = macroTimerFunc } export function nextTick(fn?: Function): Promise<void> { const id = index++ const job = fn.bind(null) queue.push(job) if (!flushing) { if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!fn && typeof Promise !== 'undefined') { return new Promise(resolve => { resolvedPromise.then(() => { if (has[id] || !queue.includes(job)) { return } queue.splice(queue.indexOf(job), 1) resolve() }) }) } }
在 Vue3 中,nextTick 的實(shí)現(xiàn)原理基于MutationObserver 和 Promise.resolve().then(),通過 MutationObserver 監(jiān)測 DOM 變化,在下一個(gè)微任務(wù)中執(zhí)行回調(diào)函數(shù)。
而如果當(dāng)前瀏覽器不支持原生 Promise,則使用 setTimeout 來模擬 Promise 的行為,并在回調(diào)函數(shù)執(zhí)行前添加一個(gè)空的定時(shí)器來強(qiáng)制推遲執(zhí)行(解決 iOS 中 setTimeout 在非激活標(biāo)簽頁中的問題)。
如果需要等待所有回調(diào)函數(shù)執(zhí)行完成,則可以通過返回一個(gè) Promise 對象來實(shí)現(xiàn)。
javascript復(fù)制代碼 export function nextTick(fn?: Function): Promise<void> { const id = index++ const job = fn.bind(null) queue.push(job) if (!flushing) { if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!fn && typeof Promise !== 'undefined') { return new Promise(resolve => { resolvedPromise.then(() => { if (has[id] || !queue.includes(job)) { return } queue.splice(queue.indexOf(job), 1) resolve() }) }) } }
總結(jié)
在 Vue3 中,nextTick 的實(shí)現(xiàn)原理基于 MutationObserver 和 Promise.resolve().then()。如果瀏覽器不支持原生 Promise,則使用 setTimeout 來模擬 Promise 的行為,并在回調(diào)函數(shù)執(zhí)行前添加一個(gè)空的定時(shí)器來強(qiáng)制推遲執(zhí)行。
結(jié)論
無論是在 Vue2 還是 Vue3 中,nextTick 都是用來處理 DOM 更新完畢后執(zhí)行回調(diào)函數(shù)的方法。在 Vue2 中,nextTick 的實(shí)現(xiàn)基于瀏覽器的異步任務(wù)隊(duì)列和微任務(wù)隊(duì)列,而在 Vue3 中,為了解決瀏覽器對 Promise 的缺陷和問題,使用 MutationObserver 和 Promise.resolve().then() 來實(shí)現(xiàn)。同時(shí),Vue3 中的 nextTick 方法也支持返回 Promise 對象,方便等待所有回調(diào)函數(shù)執(zhí)行完成后再進(jìn)行下一步操作。
需要注意的是,盡管 Vue3 中使用了 MutationObserver 和 Promise.resolve().then() 來解決 Promise 在瀏覽器中的缺陷,但在某些情況下(例如非激活標(biāo)簽頁中),仍然可能會出現(xiàn)問題。因此,在實(shí)際使用中,還需要根據(jù)具體情況選擇合適的方案。
總之,了解 nextTick 的實(shí)現(xiàn)原理可以幫助我們更好地理解 Vue 中的異步更新機(jī)制,從而更好地優(yōu)化和調(diào)試應(yīng)用程序。
以上就是Vue2和Vue3的nextTick實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Vue2和Vue3 nextTick實(shí)現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue使用axios?post發(fā)送json數(shù)據(jù)跨域請求403的解決方案
這篇文章主要介紹了vue使用axios?post發(fā)送json數(shù)據(jù)跨域請求403的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12element-ui中select組件綁定值改變,觸發(fā)change事件方法
今天小編就為大家分享一篇element-ui中select組件綁定值改變,觸發(fā)change事件方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08使用 Vue 綁定單個(gè)或多個(gè) Class 名的實(shí)例代碼
這篇文章主要介紹了使用 Vue 綁定單個(gè)或多個(gè) Class 名的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-01-01Vue + OpenLayers 快速入門學(xué)習(xí)教程
大家都知道使用 Openlayers可以很靈活自由的做出各種地圖和空間數(shù)據(jù)的展示。而且這個(gè)框架是完全免費(fèi)和開源的,本文記錄下 Vue 使用 OpenLayers 入門,使用 OpenLayers 創(chuàng)建地圖組件的相關(guān)知識,需要的朋友一起學(xué)習(xí)下吧2021-09-09Vue使用axios發(fā)送請求簡單實(shí)現(xiàn)代碼
axios是一個(gè)基于Promise的,發(fā)送http請求的一個(gè)工具庫,并不是vue中的第三方插件,使用時(shí)不能通過“Vue.use()”安裝插件,需要在原型上進(jìn)行綁定2023-04-04