vue中$nextTick原理分析


$nextTick 是 vue 中的異步更新,在官網是這樣解釋的:Vue 異步執行 DOM 更新。只要觀察到數據變化,Vue 將開啟一個隊列,並緩沖在同一事件循環中發生的所有數據改變。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對於避免不必要的計算和 DOM 操作上非常重要。然后,在下一個的事件循環“tick”中,Vue 刷新隊列並執行實際 (已去重的) 工作。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和MessageChannel,如果執行環境不支持,會采用 setTimeout(fn, 0)代替。

作用和用法:

  用法:接受一個回調函數作為參數,它的作用是將回調函數延遲到一次DOM更新之后的操作,如果沒有提供回調函數參數且在支持Promise的環境中,nextTick將返回一個Promise。

  使用場景:開發過程中,開發者需要在更新完數據之后,需要對新DOM做一些操作,其實我們當時無法對新DOM進行操作,因為這時候還沒有重新渲染。

源碼:

/**
* Defer a task to execute it asynchronously.
*/
export const nextTick = (function () {
    const callbacks = []
    let pending = false
    let timerFunc;
    function nextTickHandler () {
        pending = false
        const copies = callbacks.slice(0)
        callbacks.length = 0
        for (let i = 0; i < copies.length; i++) {
            copies[i]()
        }
    }
// the nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore if */
    if (typeof Promise !== 'undefined' && isNative(Promise)) {
        var p = Promise.resolve()
        var logError = err => { console.error(err) }
        timerFunc = () => {
            p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
            if (isIOS) setTimeout(noop)
        }
    } else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||MutationObserver.toString() === '[object MutationObserverConstructor]')) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
        var counter = 1
        var observer = new MutationObserver(nextTickHandler)
        var textNode = document.createTextNode(String(counter))
            observer.observe(textNode, {
            characterData: true
        })
        timerFunc = () => {
            counter = (counter + 1) % 2
            textNode.data = String(counter)
        }
    } else {
// fallback to setTimeout
/* istanbul ignore next */
        timerFunc = () => {
            setTimeout(nextTickHandler, 0)
        }
    }
    return function queueNextTick (cb?: Function, ctx?: Object) {
        let _resolve
        callbacks.push(() => {
            if (cb) {
                try {
                    cb.call(ctx)
                } catch (e) {
                    handleError(e, ctx, 'nextTick')
                }
            } else if (_resolve) {
                _resolve(ctx)
            }
        })
        if (!pending) {
            pending = true
            timerFunc()
        }
        if (!cb && typeof Promise !== 'undefined') {
            return new Promise((resolve, reject) => {
            _resolve = resolve
        })
    }
}
})()

會先把一些異步處理的方法收集起來,然后去監聽當前同步操作何時完成,完成之后再去執行那些異步回調;

Promise

  從源碼上看,判斷瀏覽器是否支持Promise,如果瀏覽器支持Promise,那么就用Promise.then的方式來延遲函數調用,Promise.then方法可以將函數延遲到當前函數調用棧最末端,也就是函數調用棧最后調用該函數。從而做到延遲。

MutationObserver

  MutationObserver是h5新加的一個功能,其功能是監聽dom節點的變動,在所有dom變動完成后,執行回調函數。  

  具體有一下幾點變動的監聽

    childList:子元素的變動

    attributes:屬性的變動

    characterData:節點內容或節點文本的變動

    subtree:所有下屬節點(包括子節點和子節點的子節點)的變動

    可以看出,以上代碼是創建了一個文本節點,來改變文本節點的內容來觸發的變動,因為我們在數據模型更新后,將會引起dom節點重新渲染,所以,我們加了這樣一個變動監聽,用一個文本節點的變動觸發監聽,等所有dom渲染完后,執行函數,達到我們延遲的效果。

setTimeout延遲器

 利用setTimeout的延遲原理,setTimeout(func, 0)會將func函數延遲到下一次函數調用棧的開始,也就是當前函數執行完畢后再執行該函數,因此完成了延遲功能。

 

return的函數就是我們實際使用的閉包函數,每一次添加函數,都會想callbacks這個函數數組入棧。然后監聽當前是否正在執行;

return function queueNextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) { cb.call(ctx); }
      if (_resolve) { _resolve(ctx); }
    });
    // 如果沒有函數隊列在執行才執行
    if (!pending) {
      pending = true;
      timerFunc();
    }
    // promise化
    if (!cb && typeof Promise !== 'undefined') {
      console.log('進來了')
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

 

結語:

  每天學習一點,離禿頭近一點...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM