vue中nextTick的原理

由于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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容