由于Vue DOM更新是異步執(zhí)行的,即修改數據時,視圖不會立即更新,而是會監(jiān)聽數據變化,并緩存在同一事件循環(huán)中,等同一數據循環(huán)中的所有數據變化完成之后,再統一進行視圖更新。為了確保得到更新后的DOM,所以設置了 Vue.nextTick()方法。
什么是Vue.nextTick()
是Vue的核心方法之一,官方文檔解釋如下:
在下次DOM更新循環(huán)結束之后執(zhí)行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的DOM。
源碼淺析
nextTick 源碼主要分為兩塊:能力檢測和根據能力檢測以不同方式執(zhí)行回調隊列。
1、flushCallbacks () // 該函數是對callbacks進行遍歷,然后執(zhí)行相應的回調函數
2、timerFunc // 異步執(zhí)行函數 用于異步延遲調用 flushCallbacks 函數
延遲調用優(yōu)先級如下:
Promise > MutationObserver > setImmediate > setTimeout
// 使用 MicroTask 的標識符,這里是因為火狐在<=53時 無法觸發(fā)微任務
export let isUsingMicroTask = false
// 用來存儲所有需要執(zhí)行的回調函數
const callbacks = []
// 用來標志是否正在執(zhí)行回調函數
let pending = false
// 對callbacks進行遍歷,然后執(zhí)行相應的回調函數
function flushCallbacks () {
pending = false
// 這里拷貝的原因是:
// 有的cb 執(zhí)行過程中又會往callbacks中加入內容
// 比如 $nextTick的回調函數里還有$nextTick
// 后者的應該放到下一輪的nextTick 中執(zhí)行
// 所以拷貝一份當前的,遍歷執(zhí)行完當前的即可,避免無休止的執(zhí)行下去
const copies = callbcks.slice(0)
callbacks.length = 0
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc // 異步執(zhí)行函數 用于異步延遲調用 flushCallbacks 函數
// 優(yōu)先使用 Promise
if(typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// IOS 的UIWebView, Promise.then 回調被推入 microTask 隊列,但是隊列可能不會如期執(zhí)行
// 因此,添加一個空計時器強制執(zhí)行 microTask
if(isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
// 當 原生Promise 不可用時,使用 原生MutationObserver
let counter = 1
// 創(chuàng)建MO實例,監(jiān)聽到DOM變動后會執(zhí)行回調flushCallbacks
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true // 設置true 表示觀察目標的改變
})
// 每次執(zhí)行timerFunc 都會讓文本節(jié)點的內容在 0/1之間切換
// 切換之后將新值復制到 MO 觀測的文本節(jié)點上
// 節(jié)點內容變化會觸發(fā)回調
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter) // 觸發(fā)回調
}
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 對外暴露了nextTick這一個參數,所以每次調用Vue.nextTick時會執(zhí)行:
- 把傳入的回調函數cb壓入callbacks數組
- 執(zhí)行timerFunc函數,延遲調用 flushCallbacks 函數
- 遍歷執(zhí)行 callbacks 數組中的所有函數,這里的 callbacks 沒有直接在 nextTick 中執(zhí)行回調函數的原因是保證在同一個 tick 內多次執(zhí)行nextTick,不會開啟多個異步任務,而是把這些異步任務都壓成一個同步任務,在下一個 tick 執(zhí)行完畢。
export function nextTick(cb? Function, ctx: Object) {
let _resolve
// cb 回調函數會統一處理壓入callbacks數組
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()
}
// 當不傳入 cb 參數時,提供一個promise化的調用
// 如nextTick().then(() => {})
// 當_resolve執(zhí)行時,就會跳轉到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