Vue中會(huì)用到一個(gè)nextTick()的方法,在Vue實(shí)例中更改數(shù)據(jù)之后需要改動(dòng)DOM,如果需要操作更新后的DOM,就要使用nextTick(),確保內(nèi)部的函數(shù)是在下一輪循環(huán)中才會(huì)執(zhí)行。
官網(wǎng)的定義如下:
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。
Vue.nextTick
nextTick使用方法有回調(diào)和Promise兩種,更常見(jiàn)的是在實(shí)例調(diào)用this.$nextTick(() => {回調(diào)方法})。
淺談nextTick原理
在項(xiàng)目文件夾中,可以找到nextTick源碼,具體地址是node_modules\vue\src\core\util\next-tick.js,代碼不長(zhǎng),添加自己理解的備注后代碼如下:
import { noop } from 'shared/util'
// 錯(cuò)誤處理函數(shù)
import { handleError } from './error'
// 是否是IE、IOS、內(nèi)置函數(shù)
import { isIE, isIOS, isNative } from './env'
// 使用微任務(wù)標(biāo)識(shí)符
export let isUsingMicroTask = false
// 存回調(diào)函數(shù)的數(shù)組
const callbacks = []
// pending 是一個(gè)標(biāo)識(shí)符,
let pending = false
// 遍歷執(zhí)行回調(diào)函數(shù)
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 根據(jù)下面的判斷來(lái)給timerFunc定義
let timerFunc
// 首先第一順位是使用Promise實(shí)現(xiàn)延遲回調(diào),并且判斷是否是原生Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
// 將timerFunc定義為Promise,并且延遲通過(guò)lushCallbacks執(zhí)行所有回調(diào)
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
// 設(shè)置使用微任務(wù)標(biāo)識(shí)符 因?yàn)镻romise.then()是一個(gè)微任務(wù)
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
))
// 當(dāng)不是原生Promise時(shí),第二順位是使用MutationObserver API
{
//
let counter = 1
// 創(chuàng)建MutationObserver,用于監(jiān)聽(tīng)DOM變化,并且執(zhí)行flushCallbacks函數(shù)
const observer = new MutationObserver(flushCallbacks)
// 設(shè)置一個(gè)觀測(cè)節(jié)點(diǎn)
const textNode = document.createTextNode(String(counter))
// 將觀測(cè)節(jié)點(diǎn)設(shè)置為觀察目標(biāo)的改變
observer.observe(textNode, {
characterData: true
})
// 定義timerFunc
timerFunc = () => {
// counter會(huì)在0/1之間切換
counter = (counter + 1) % 2
// 并且將新counter寫(xiě)入到觀測(cè)節(jié)點(diǎn),這個(gè)數(shù)值的變化就會(huì)觸發(fā)MutationObserver的檢測(cè),從而觸發(fā)回調(diào)執(zhí)行
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate))
{
// 當(dāng)Promise和MutationObserver,都無(wú)法使用時(shí),用setImmediate定時(shí)器,這里就算宏任務(wù)了
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 上面三種情況都不可時(shí)候用的時(shí)候,就用最普通的定時(shí)器,宏任務(wù)切換到下一次循環(huán)
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 實(shí)際暴露出去的函數(shù)
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 統(tǒng)一處理回調(diào)函數(shù)
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
// 錯(cuò)誤處理函數(shù)
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pending 為false 說(shuō)明本輪事件循環(huán)中沒(méi)有執(zhí)行過(guò)timerFunc()
if (!pending) {
pending = true
timerFunc()
}
// 如果沒(méi)傳入cb回調(diào)函數(shù),就自動(dòng)創(chuàng)建個(gè)Promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
代碼整體是好理解的,具體實(shí)現(xiàn)異步的方法順位是Promise > MutationObserver > setImmediate > setTimeout,就是使用這4種方法,包裹c(diǎn)allback函數(shù),使得callback可以異步執(zhí)行,其中MutationObserver是HTML5中添加,用于監(jiān)視DOM變動(dòng)的接口,它可以監(jiān)聽(tīng)一個(gè)DOM對(duì)象上發(fā)生的子節(jié)點(diǎn)刪除、屬性修改、文本內(nèi)容修改等。
