由于Vue DOM更新是異步執(zhí)行的莲趣,即修改數(shù)據(jù)時(shí),視圖不會(huì)立即更新饱溢,而是會(huì)監(jiān)聽數(shù)據(jù)變化喧伞,并緩存在同一事件循環(huán)中,等同一數(shù)據(jù)循環(huán)中的所有數(shù)據(jù)變化完成之后绩郎,再統(tǒng)一進(jìn)行視圖更新潘鲫。為了確保得到更新后的DOM,所以設(shè)置了 Vue.nextTick()方法肋杖。
什么是Vue.nextTick()
是Vue的核心方法之一溉仑,官方文檔解釋如下:
在下次DOM更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法状植,獲取更新后的DOM浊竟。
源碼淺析
nextTick 源碼主要分為兩塊:能力檢測(cè)和根據(jù)能力檢測(cè)以不同方式執(zhí)行回調(diào)隊(duì)列怨喘。
1、flushCallbacks () // 該函數(shù)是對(duì)callbacks進(jìn)行遍歷振定,然后執(zhí)行相應(yīng)的回調(diào)函數(shù)
2必怜、timerFunc // 異步執(zhí)行函數(shù) 用于異步延遲調(diào)用 flushCallbacks 函數(shù)
延遲調(diào)用優(yōu)先級(jí)如下:
Promise > MutationObserver > setImmediate > setTimeout
// 使用 MicroTask 的標(biāo)識(shí)符,這里是因?yàn)榛鸷?lt;=53時(shí) 無法觸發(fā)微任務(wù)
export let isUsingMicroTask = false
// 用來存儲(chǔ)所有需要執(zhí)行的回調(diào)函數(shù)
const callbacks = []
// 用來標(biāo)志是否正在執(zhí)行回調(diào)函數(shù)
let pending = false
// 對(duì)callbacks進(jìn)行遍歷后频,然后執(zhí)行相應(yīng)的回調(diào)函數(shù)
function flushCallbacks () {
pending = false
// 這里拷貝的原因是:
// 有的cb 執(zhí)行過程中又會(huì)往callbacks中加入內(nèi)容
// 比如 $nextTick的回調(diào)函數(shù)里還有$nextTick
// 后者的應(yīng)該放到下一輪的nextTick 中執(zhí)行
// 所以拷貝一份當(dāng)前的梳庆,遍歷執(zhí)行完當(dāng)前的即可,避免無休止的執(zhí)行下去
const copies = callbcks.slice(0)
callbacks.length = 0
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc // 異步執(zhí)行函數(shù) 用于異步延遲調(diào)用 flushCallbacks 函數(shù)
// 優(yōu)先使用 Promise
if(typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// IOS 的UIWebView, Promise.then 回調(diào)被推入 microTask 隊(duì)列徘郭,但是隊(duì)列可能不會(huì)如期執(zhí)行
// 因此靠益,添加一個(gè)空計(jì)時(shí)器強(qiáng)制執(zhí)行 microTask
if(isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
// 當(dāng) 原生Promise 不可用時(shí),使用 原生MutationObserver
let counter = 1
// 創(chuàng)建MO實(shí)例残揉,監(jiān)聽到DOM變動(dòng)后會(huì)執(zhí)行回調(diào)flushCallbacks
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true // 設(shè)置true 表示觀察目標(biāo)的改變
})
// 每次執(zhí)行timerFunc 都會(huì)讓文本節(jié)點(diǎn)的內(nèi)容在 0/1之間切換
// 切換之后將新值復(fù)制到 MO 觀測(cè)的文本節(jié)點(diǎn)上
// 節(jié)點(diǎn)內(nèi)容變化會(huì)觸發(fā)回調(diào)
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter) // 觸發(fā)回調(diào)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
3胧后、nextTick(cb? Function, ctx: Object) {}
next-tick.js 對(duì)外暴露了nextTick這一個(gè)參數(shù),所以每次調(diào)用Vue.nextTick時(shí)會(huì)執(zhí)行:
- 把傳入的回調(diào)函數(shù)cb壓入callbacks數(shù)組
- 執(zhí)行timerFunc函數(shù)抱环,延遲調(diào)用 flushCallbacks 函數(shù)
- 遍歷執(zhí)行 callbacks 數(shù)組中的所有函數(shù)壳快,這里的 callbacks 沒有直接在 nextTick 中執(zhí)行回調(diào)函數(shù)的原因是保證在同一個(gè) tick 內(nèi)多次執(zhí)行nextTick,不會(huì)開啟多個(gè)異步任務(wù)镇草,而是把這些異步任務(wù)都?jí)撼梢粋€(gè)同步任務(wù)眶痰,在下一個(gè) tick 執(zhí)行完畢。
export function nextTick(cb? Function, ctx: Object) {
let _resolve
// cb 回調(diào)函數(shù)會(huì)統(tǒng)一處理壓入callbacks數(shù)組
callbacks.push(() => {
if(cb) {
try {
cb.call(ctx)
} catch(e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pending 為false 說明本輪事件循環(huán)中沒有執(zhí)行過timerFunc()
if(!pending) {
pending = true
timerFunc()
}
// 當(dāng)不傳入 cb 參數(shù)時(shí)梯啤,提供一個(gè)promise化的調(diào)用
// 如nextTick().then(() => {})
// 當(dāng)_resolve執(zhí)行時(shí)竖伯,就會(huì)跳轉(zhuǎn)到then邏輯中
if(!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
詳見參考以下鏈接:
https://blog.csdn.net/qq_38290251/article/details/107550899
https://zhuanlan.zhihu.com/p/174396758
https://blog.csdn.net/chenzeze0707/article/details/90083725