首頁  >  文章  >  web前端  >  Vue中nextTick有什麼功能? nextTick的簡單實現

Vue中nextTick有什麼功能? nextTick的簡單實現

不言
不言轉載
2018-11-24 14:31:405901瀏覽

這篇文章帶給大家的內容是關於Vue中nextTick有什麼功能?如何實現? ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

nextTick是Vue的核心功能,在Vue內部實作中也常用到nextTick。但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的功能。

那麼,我們就先來看看nextTick是什麼。

nextTick功能

看看官方文件的描述:

在下次 DOM 更新循環結束之後執行延遲回調。在修改資料之後立即使用這個方法,以取得更新後的 DOM。

再看看官方範例:

// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
  .then(function () {
    // DOM 更新了
  })

2.1.0 起新增:如果沒有提供回呼且在支援 Promise 的環境中,則傳回一個 Promise。請注意 Vue 不自備 Promise 的 polyfill,所以如果你的目標瀏覽器不原生支援 Promise (IE:你們都看我幹嘛),你得自己提供 polyfill。

可以看到,nextTick主要功能就是改變資料後讓回呼函數作用在dom更新後。很多人一看到這裡就懵逼了,為什麼需要在dom更新後再執行回呼函數,我修改了資料後,不是dom自動就更新了嗎?

這個和JS中的Event Loop有關,網路教學不計其數,在此就不再贅述了。建議明白Event Loop後再繼續往下閱讀本文。

舉個實際的例子:

我們有個有分頁器的表格,每次翻頁需要選取第一項。正常情況下,我們想的是點擊翻頁器,向後台取得數據,更新表格數據,操縱表格API選取第一項。

但是,你會發現,表格資料是更新了,但是並沒有選取第一項。因為,當你選取第一項時,雖然資料更新了,但DOM並沒有更新。此時,你可以使用nextTick,在DOM更新後再操縱表格第一項的選取。

那麼,nextTick到底做了什麼了才能實現在DOM更新後執行回呼函數?

原始碼分析

nextTick的原始碼位於src/core/util/next-tick.js,總計118行,十分的短小精悍,十分適合初次閱讀原始碼的同學。

nextTick原始碼主要分為兩塊:

#1.能力偵測

#2.根據能力偵測以不同方式執行回呼佇列

能力偵測

這一塊其實很簡單,眾所周知,Event Loop分成宏任務(macro task)以及微任務( micro task),不管執行宏任務還是微任務,完成後都會進入下一個tick,並在兩個tick之間執行UI渲染。

但是,巨集任務耗費的時間是大於微任務的,所以在瀏覽器支援的情況下,優先使用微任務。如果瀏覽器不支援微任務,使用巨集任務;但是,各種巨集任務之間也有效率的不同,需要根據瀏覽器的支援情況,使用不同的巨集任務。

nextTick在能力偵測這一塊,就是遵循的這種想法。

// Determine (macro) task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
/* istanbul ignore if */
// 如果浏览器不支持Promise,使用宏任务来执行nextTick回调函数队列
// 能力检测,测试浏览器是否支持原生的setImmediate(setImmediate只在IE中有效)
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 如果支持,宏任务( macro task)使用setImmediate
  macroTimerFunc = () => {
    setImmediate(flushCallbacks)
  }
  // 同上
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  /* istanbul ignore next */
  // 都不支持的情况下,使用setTimeout
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

首先,偵測瀏覽器是否支援setImmediate,不支援就使用MessageChannel,再不支援只能使用效率最差但是相容性最好的setTimeout了。

之後,偵測瀏覽器是否支援Promise,如果支持,則使用Promise來執行回呼函數佇列,畢竟微任務速度大於巨集任務。如果不支援的話,就只能使用巨集任務來執行回呼函數佇列。

執行回呼函數佇列

執行回呼函數佇列的程式碼剛好在一頭一尾

// 回调函数队列
const callbacks = []
// 异步锁
let pending = false

// 执行回调函数
function flushCallbacks () {
  // 重置异步锁
  pending = false
  // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 执行回调函数队列
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

...

// 我们调用的nextTick函数
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 将回调函数推入回调队列
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, &#39;nextTick&#39;)
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列
  if (!pending) {
    pending = true
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  // 2.1.0新增,如果没有提供回调,并且支持Promise,返回一个Promise
  if (!cb && typeof Promise !== &#39;undefined&#39;) {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

整體流程就是,接收回呼函數,將回呼函數推入回呼函數佇列中。

同時,在接收第一個回呼函數時,執行能力偵測中對應的非同步方法(非同步方法中呼叫了回呼函數佇列)。

如何保證只在接收第一個回呼函數時執行非同步方法?

nextTick原始碼中使用了一個非同步鎖的概念,即接收第一個回呼函數時,先關上鎖,執行非同步方法。此時,瀏覽器處於等待執行完同步程式碼就會執行非同步程式碼的情況。

打個比喻:相當於一群旅客準備上車,當第一個旅客上車的時候,車開始發動,準備出發,等到所有旅客都上車後,就可以正式開車了。

當然執行flushCallbacks函數時有個難以理解的點,也就是:為什麼需要備份回呼函數佇列?執行的也是備份的回呼函數佇列?

因為,會出現這麼一種狀況:nextTick套用nextTick。如果flushCallbacks不做特殊處理,直接循環執行回呼函數,會導致裡面nextTick中的回呼函數會進入回呼佇列。這就相當於,下一個班車的旅客上了上一個班車。

實作一個簡易的nextTick

說了這麼多,我們來實作一個簡單的nextTick:

let callbacks = []
let pending = false

function nextTick (cb) {
    callbacks.push(cb)

    if (!pending) {
        pending = true
        setTimeout(flushCallback, 0)
    }
}

function flushCallback () {
    pending = false
    let copies = callbacks.slice()
    callbacks.length = 0
    copies.forEach(copy => {
        copy()
    })
}

可以看到,在簡易版的nextTick中,透過nextTick接收回呼函數,透過setTimeout來非同步執行回呼函數。透過這種方式,可以實現在下一個tick中執行回調函數,即在UI重新渲染後執行回呼函數。

以上是Vue中nextTick有什麼功能? nextTick的簡單實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除