溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue中nextTick有什么作用

發(fā)布時(shí)間:2020-10-19 14:27:38 來(lái)源:億速云 閱讀:205 作者:小新 欄目:web開(kāi)發(fā)

這篇文章給大家分享的是有關(guān)Vue中nextTick有什么作用的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。

nextTick是Vue的一個(gè)核心功能,在Vue內(nèi)部實(shí)現(xiàn)中也經(jīng)常用到nextTick。但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作用。

那么,我們就先來(lái)看看nextTick是什么。

nextTick功能

看看官方文檔的描述:

在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。

再看看官方示例:

// 修改數(shù)據(jù)
vm.msg = 'Hello'
// DOM 還沒(méi)有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作為一個(gè) Promise 使用 (2.1.0 起新增,詳見(jiàn)接下來(lái)的提示)
Vue.nextTick()
  .then(function () {
    // DOM 更新了
  })

2.1.0 起新增:如果沒(méi)有提供回調(diào)且在支持 Promise 的環(huán)境中,則返回一個(gè) Promise。請(qǐng)注意 Vue 不自帶 Promise 的 polyfill,所以如果你的目標(biāo)瀏覽器不原生支持 Promise (IE:你們都看我干嘛),你得自己提供 polyfill。

可以看到,nextTick主要功能就是改變數(shù)據(jù)后讓回調(diào)函數(shù)作用于dom更新后。很多人一看到這里就懵逼了,為什么需要在dom更新后再執(zhí)行回調(diào)函數(shù),我修改了數(shù)據(jù)后,不是dom自動(dòng)就更新了嗎?

這個(gè)和JS中的Event Loop有關(guān),網(wǎng)上教程不計(jì)其數(shù),在此就不再贅述了。建議明白Event Loop后再繼續(xù)向下閱讀本文。

舉個(gè)實(shí)際的例子:

我們有個(gè)帶有分頁(yè)器的表格,每次翻頁(yè)需要選中第一項(xiàng)。正常情況下,我們想的是點(diǎn)擊翻頁(yè)器,向后臺(tái)獲取數(shù)據(jù),更新表格數(shù)據(jù),操縱表格API選中第一項(xiàng)。

但是,你會(huì)發(fā)現(xiàn),表格數(shù)據(jù)是更新了,但是并沒(méi)有選中第一項(xiàng)。因?yàn)?,你選中第一項(xiàng)時(shí),雖然數(shù)據(jù)更新了,但是DOM并沒(méi)有更新。此時(shí),你可以使用nextTick,在DOM更新后再操縱表格第一項(xiàng)的選中。

那么,nextTick到底做了什么了才能實(shí)現(xiàn)在DOM更新后執(zhí)行回調(diào)函數(shù)?

源碼分析

nextTick的源碼位于src/core/util/next-tick.js,總計(jì)118行,十分的短小精悍,十分適合初次閱讀源碼的同學(xué)。

nextTick源碼主要分為兩塊:

1.能力檢測(cè)

2.根據(jù)能力檢測(cè)以不同方式執(zhí)行回調(diào)隊(duì)列

能力檢測(cè)

這一塊其實(shí)很簡(jiǎn)單,眾所周知,Event Loop分為宏任務(wù)(macro task)以及微任務(wù)( micro task),不管執(zhí)行宏任務(wù)還是微任務(wù),完成后都會(huì)進(jìn)入下一個(gè)tick,并在兩個(gè)tick之間執(zhí)行UI渲染。

但是,宏任務(wù)耗費(fèi)的時(shí)間是大于微任務(wù)的,所以在瀏覽器支持的情況下,優(yōu)先使用微任務(wù)。如果瀏覽器不支持微任務(wù),使用宏任務(wù);但是,各種宏任務(wù)之間也有效率的不同,需要根據(jù)瀏覽器的支持情況,使用不同的宏任務(wù)。

nextTick在能力檢測(cè)這一塊,就是遵循的這種思想。

// 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,使用宏任務(wù)來(lái)執(zhí)行nextTick回調(diào)函數(shù)隊(duì)列
// 能力檢測(cè),測(cè)試瀏覽器是否支持原生的setImmediate(setImmediate只在IE中有效)
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 如果支持,宏任務(wù)( 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)
  }
}

首先,檢測(cè)瀏覽器是否支持setImmediate,不支持就使用MessageChannel,再不支持只能使用效率最差但是兼容性最好的setTimeout了。

之后,檢測(cè)瀏覽器是否支持Promise,如果支持,則使用Promise來(lái)執(zhí)行回調(diào)函數(shù)隊(duì)列,畢竟微任務(wù)速度大于宏任務(wù)。如果不支持的話,就只能使用宏任務(wù)來(lái)執(zhí)行回調(diào)函數(shù)隊(duì)列。

執(zhí)行回調(diào)函數(shù)隊(duì)列

執(zhí)行回調(diào)函數(shù)隊(duì)列的代碼剛好在一頭一尾

// 回調(diào)函數(shù)隊(duì)列
const callbacks = []
// 異步鎖
let pending = false

// 執(zhí)行回調(diào)函數(shù)
function flushCallbacks () {
  // 重置異步鎖
  pending = false
  // 防止出現(xiàn)nextTick中包含nextTick時(shí)出現(xiàn)問(wèn)題,在執(zhí)行回調(diào)函數(shù)隊(duì)列前,提前復(fù)制備份,清空回調(diào)函數(shù)隊(duì)列
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 執(zhí)行回調(diào)函數(shù)隊(duì)列
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

...

// 我們調(diào)用的nextTick函數(shù)
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 將回調(diào)函數(shù)推入回調(diào)隊(duì)列
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 如果異步鎖未鎖上,鎖上異步鎖,調(diào)用異步函數(shù),準(zhǔn)備等同步函數(shù)執(zhí)行完后,就開(kāi)始執(zhí)行回調(diào)函數(shù)隊(duì)列
  if (!pending) {
    pending = true
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  // 2.1.0新增,如果沒(méi)有提供回調(diào),并且支持Promise,返回一個(gè)Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

總體流程就是,接收回調(diào)函數(shù),將回調(diào)函數(shù)推入回調(diào)函數(shù)隊(duì)列中。

同時(shí),在接收第一個(gè)回調(diào)函數(shù)時(shí),執(zhí)行能力檢測(cè)中對(duì)應(yīng)的異步方法(異步方法中調(diào)用了回調(diào)函數(shù)隊(duì)列)。

如何保證只在接收第一個(gè)回調(diào)函數(shù)時(shí)執(zhí)行異步方法?

nextTick源碼中使用了一個(gè)異步鎖的概念,即接收第一個(gè)回調(diào)函數(shù)時(shí),先關(guān)上鎖,執(zhí)行異步方法。此時(shí),瀏覽器處于等待執(zhí)行完同步代碼就執(zhí)行異步代碼的情況。

打個(gè)比喻:相當(dāng)于一群旅客準(zhǔn)備上車,當(dāng)?shù)谝粋€(gè)旅客上車的時(shí)候,車開(kāi)始發(fā)動(dòng),準(zhǔn)備出發(fā),等到所有旅客都上車后,就可以正式開(kāi)車了。

當(dāng)然執(zhí)行flushCallbacks函數(shù)時(shí)有個(gè)難以理解的點(diǎn),即:為什么需要備份回調(diào)函數(shù)隊(duì)列?執(zhí)行的也是備份的回調(diào)函數(shù)隊(duì)列?

因?yàn)?,?huì)出現(xiàn)這么一種情況:nextTick套用nextTick。如果flushCallbacks不做特殊處理,直接循環(huán)執(zhí)行回調(diào)函數(shù),會(huì)導(dǎo)致里面nextTick中的回調(diào)函數(shù)會(huì)進(jìn)入回調(diào)隊(duì)列。這就相當(dāng)于,下一個(gè)班車的旅客上了上一個(gè)班車。

實(shí)現(xiàn)一個(gè)簡(jiǎn)易的nextTick

說(shuō)了這么多,我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的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()
    })
}

可以看到,在簡(jiǎn)易版的nextTick中,通過(guò)nextTick接收回調(diào)函數(shù),通過(guò)setTimeout來(lái)異步執(zhí)行回調(diào)函數(shù)。通過(guò)這種方式,可以實(shí)現(xiàn)在下一個(gè)tick中執(zhí)行回調(diào)函數(shù),即在UI重新渲染后執(zhí)行回調(diào)函數(shù)。

感謝各位的閱讀!關(guān)于Vue中nextTick有什么作用就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI