淺析vue中$nextTick的作用與原理
一、為什么使用nextTick
因?yàn)?vue 采用的異步更新策略,當(dāng)監(jiān)聽(tīng)到數(shù)據(jù)發(fā)生變化的時(shí)候不會(huì)立即去更新DOM,
而是開(kāi)啟一個(gè)任務(wù)隊(duì)列,并緩存在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更;
這種做法帶來(lái)的好處就是可以將多次數(shù)據(jù)更新合并成一次,減少操作DOM的次數(shù),
如果不采用這種方法,假設(shè)數(shù)據(jù)改變100次就要去更新100次DOM,而頻繁的DOM更新是很耗性能的。
二、nextTick 作用
nextTick 接收一個(gè)回調(diào)函數(shù)作為參數(shù),并將這個(gè)回調(diào)函數(shù)延遲到DOM更新后才執(zhí)行;
使用場(chǎng)景:想要操作 基于最新數(shù)據(jù)生成的DOM 時(shí),就將這個(gè)操作放在 nextTick 的回調(diào)中;
下面是一個(gè)簡(jiǎn)單示例,展示了如何使用nextTick:
new Vue({ data() { return { message: 'Hello, Vue!' } }, mounted() { this.message = 'Modified message' this.$nextTick(() => { // 在DOM更新之后執(zhí)行的操作 console.log(this.$el.textContent) // 輸出:"Modified message" }) } })
代碼解析:
第一次 console.log 的時(shí)候,獲取的到的是舊值,這是因?yàn)?value 數(shù)據(jù)發(fā)生變化的時(shí)候,Vue 沒(méi)有立刻去更新 DOM ,而是將修改數(shù)據(jù)的操作放在了一個(gè)異步操作隊(duì)列中,如果一直修改相同數(shù)據(jù),異步操作隊(duì)列還會(huì)進(jìn)行去重,等待同一事件循環(huán)中的所有數(shù)據(jù)變化完成之后,會(huì)將隊(duì)列中的事件拿來(lái)進(jìn)行處理,進(jìn)行 DOM 的更新
第二次的 console.log 是放到 this.$nextTick 回調(diào)函數(shù)中的,此時(shí)獲取到的是新值,是因?yàn)?nextTick 的回調(diào)函數(shù)是在 DOM 更新之后觸發(fā)的
三、nextTick 原理
將傳入的回調(diào)函數(shù)包裝成異步任務(wù),異步任務(wù)又分微任務(wù)和宏任務(wù),為了盡快執(zhí)行所以優(yōu)先選擇微任務(wù);
nextTick 提供了四種異步方法 Promise.then、MutationObserver、setImmediate、setTimeout(fn,0)
源碼解讀
源碼位置 core/util/next-tick
源碼并不復(fù)雜,三個(gè)函數(shù),60幾行代碼,沉下心去看!
Tips:為了便于理解我調(diào)整了源碼中 nextTick、timerFunc、flushCallbacks 三個(gè)函數(shù)的書(shū)寫(xiě)順序
import { noop } from 'shared/util' import { handleError } from './error' import { isIE, isIOS, isNative } from './env' // 上面三行與核心代碼關(guān)系不大,了解即可 // noop 表示一個(gè)無(wú)操作空函數(shù),用作函數(shù)默認(rèn)值,防止傳入 undefined 導(dǎo)致報(bào)錯(cuò) // handleError 錯(cuò)誤處理函數(shù) // isIE, isIOS, isNative 環(huán)境判斷函數(shù), // isNative 判斷某個(gè)屬性或方法是否原生支持,如果不支持或通過(guò)第三方實(shí)現(xiàn)支持都會(huì)返回 false export let isUsingMicroTask = false // 標(biāo)記 nextTick 最終是否以微任務(wù)執(zhí)行 const callbacks = [] // 存放調(diào)用 nextTick 時(shí)傳入的回調(diào)函數(shù) let pending = false // 標(biāo)記是否已經(jīng)向任務(wù)隊(duì)列中添加了一個(gè)任務(wù),如果已經(jīng)添加了就不能再添加了 // 當(dāng)向任務(wù)隊(duì)列中添加了任務(wù)時(shí),將 pending 置為 true,當(dāng)任務(wù)被執(zhí)行時(shí)將 pending 置為 false // // 聲明 nextTick 函數(shù),接收一個(gè)回調(diào)函數(shù)和一個(gè)執(zhí)行上下文作為參數(shù) // 回調(diào)的 this 自動(dòng)綁定到調(diào)用它的實(shí)例上 export function nextTick(cb?: Function, ctx?: Object) { let _resolve // 將傳入的回調(diào)函數(shù)存放到數(shù)組中,后面會(huì)遍歷執(zhí)行其中的回調(diào) callbacks.push(() => { if (cb) { // 對(duì)傳入的回調(diào)進(jìn)行 try catch 錯(cuò)誤捕獲 try { cb.call(ctx) } catch (e) { // 進(jìn)行統(tǒng)一的錯(cuò)誤處理 handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // 如果當(dāng)前沒(méi)有在 pending 的回調(diào), // 就執(zhí)行 timeFunc 函數(shù)選擇當(dāng)前環(huán)境優(yōu)先支持的異步方法 if (!pending) { pending = true timerFunc() } // 如果沒(méi)有傳入回調(diào),并且當(dāng)前環(huán)境支持 promise,就返回一個(gè) promise // 在返回的這個(gè) promise.then 中 DOM 已經(jīng)更新好了, if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } // 判斷當(dāng)前環(huán)境優(yōu)先支持的異步方法,優(yōu)先選擇微任務(wù) // 優(yōu)先級(jí):Promise---> MutationObserver---> setImmediate---> setTimeout // setTimeout 可能產(chǎn)生一個(gè) 4ms 的延遲,而 setImmediate 會(huì)在主線程執(zhí)行完后立刻執(zhí)行 // setImmediate 在 IE10 和 node 中支持 // 當(dāng)在同一輪事件循環(huán)中多次調(diào)用 nextTick 時(shí) ,timerFunc 只會(huì)執(zhí)行一次 let timerFunc // 判斷當(dāng)前環(huán)境是否原生支持 promise if (typeof Promise !== 'undefined' && isNative(Promise)) { // 支持 promise const p = Promise.resolve() timerFunc = () => { // 用 promise.then 把 flushCallbacks 函數(shù)包裹成一個(gè)異步微任務(wù) p.then(flushCallbacks) if (isIOS) setTimeout(noop) // 這里的 setTimeout 是用來(lái)強(qiáng)制刷新微任務(wù)隊(duì)列的 // 因?yàn)樵?ios 下 promise.then 后面沒(méi)有宏任務(wù)的話,微任務(wù)隊(duì)列不會(huì)刷新 } // 標(biāo)記當(dāng)前 nextTick 使用的微任務(wù) isUsingMicroTask = true // 如果不支持 promise,就判斷是否支持 MutationObserver // 不是IE環(huán)境,并且原生支持 MutationObserver,那也是一個(gè)微任務(wù) } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 // new 一個(gè) MutationObserver 類 const observer = new MutationObserver(flushCallbacks) // 創(chuàng)建一個(gè)文本節(jié)點(diǎn) const textNode = document.createTextNode(String(counter)) // 監(jiān)聽(tīng)這個(gè)文本節(jié)點(diǎn),當(dāng)數(shù)據(jù)發(fā)生變化就執(zhí)行 flushCallbacks observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) // 數(shù)據(jù)更新 } isUsingMicroTask = true // 標(biāo)記當(dāng)前 nextTick 使用的微任務(wù) // 判斷當(dāng)前環(huán)境是否原生支持 setImmediate } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // 以上三種都不支持就選擇 setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } } // 如果多次調(diào)用 nextTick,會(huì)依次執(zhí)行上面的方法,將 nextTick 的回調(diào)放在 callbacks 數(shù)組中 // 最后通過(guò) flushCallbacks 函數(shù)遍歷 callbacks 數(shù)組的拷貝并執(zhí)行其中的回調(diào) function flushCallbacks() { pending = false const copies = callbacks.slice(0) // 拷貝一份 callbacks callbacks.length = 0 // 清空 callbacks for (let i = 0; i < copies.length; i++) { // 遍歷執(zhí)行傳入的回調(diào) copies[i]() } } // 為什么要拷貝一份 callbacks // 用 callbacks.slice(0) 將 callbacks 拷貝出來(lái)一份, // 是因?yàn)榭紤]到在 nextTick 回調(diào)中可能還會(huì)調(diào)用 nextTick 的情況, // 如果在 nextTick 回調(diào)中又調(diào)用了一次 nextTick,則又會(huì)向 callbacks 中添加回調(diào), // 而 nextTick 回調(diào)中的 nextTick 應(yīng)該放在下一輪執(zhí)行, // 否則就可能出現(xiàn)一直循環(huán)的情況, // 所以需要將 callbacks 復(fù)制一份出來(lái)然后清空,再遍歷備份列表執(zhí)行回調(diào)
到此這篇關(guān)于淺析vue中$nextTick的作用與原理的文章就介紹到這了,更多相關(guān)vue $nextTick內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談VueUse中useAsyncState的實(shí)現(xiàn)原理
useAsyncState?是 VueUse 庫(kù)中提供的一個(gè)實(shí)用工具,它用于處理異步狀態(tài),本文主要介紹了VueUse中useAsyncState的實(shí)現(xiàn)及其原理,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08web前端vue之vuex單獨(dú)一文件使用方式實(shí)例詳解
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。這篇文章主要介紹了web前端vue:vuex單獨(dú)一文件使用方式,需要的朋友可以參考下2018-01-01Vue純前端如何實(shí)現(xiàn)導(dǎo)出簡(jiǎn)單Excel表格的功能
這篇文章主要介紹了如何在Vue項(xiàng)目中使用vue-json-excel插件實(shí)現(xiàn)Excel表格的導(dǎo)出功能,包括安裝依賴、引入插件、使用組件、設(shè)置表頭和數(shù)據(jù)、處理空數(shù)據(jù)情況、源代碼修改以解決常見(jiàn)問(wèn)題,需要的朋友可以參考下2025-01-01vue-element-admin關(guān)閉eslint的校驗(yàn)方式
這篇文章主要介紹了vue-element-admin關(guān)閉eslint的校驗(yàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08vue3中setup-script的應(yīng)用實(shí)例
script-setup是一個(gè)比較有爭(zhēng)議的新特性,作為 setup 函數(shù)的語(yǔ)法糖,褒貶不一,不過(guò)經(jīng)歷了幾次迭代之后,目前在體驗(yàn)上來(lái)說(shuō),感受還是非常棒的,這篇文章主要給大家介紹了關(guān)于vue3中setup-script應(yīng)用的相關(guān)資料,需要的朋友可以參考下2022-01-01教你使用vue-cli快速構(gòu)建的小說(shuō)閱讀器
這篇文章主要介紹了vue-cli構(gòu)建的小說(shuō)閱讀器,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05