18.你真的了解nexttick()吗

54 阅读3分钟

在vue中我们知道在数据更新后,我们想要获取dom进行一些操作的话,是不能直接通过ref获取的,因为这个时候dom并没有更新,所有我们需要等dom更新完了再去获取dom,为什么dom不是数据更新马上同步更新呢?因为如果我们将一个数据修改三次,dom连续更新3次是十分浪费性能的.当数据变化后,把 watcher.update 函数存放进 nextTick 的 回调数组中,并且会做过滤。 通过 watcher.id 来判断 回调数组 中是否已经存在这个 watcher 的更新函数不存在,才 push。 之后 nextTick时 遍历回调数组,便会执行了更新。 所以当三次修改数据的时候,会 push 回调数组 三个 watcher.update,但是只有第一次是 push 成功的,其他的会被过滤掉,因为已经存在了。 所以,不管你修改多少次数据,nextTick 的回调数组中只存在唯一一个 watcher.update,从而页面只会更新一次。

nexttick一定是微任务吗

no,正常情况下都是微任务,但是如果游览器都不支持微任务,那么他肯定不是的. 检测浏览器是否支持Promise,如果支持,则使用Promise来执行回调函数队列,毕竟微任务速度大于宏任务。如果不支持的话,就只能使用宏任务来执行回调函数队列。

// 如果浏览器不支持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)
  }
}

在宏任务的情况下 vue也会根据支持度选择不同的方法执行任务. setImmediate>MessageChannel>setTimeout

// 回调函数队列
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, 'nextTick')
      }
    } 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 !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }

接收回调函数,将回调函数推入回调函数队列中。 判断当前环境是否支持原生Promise,原生MutationObserver,试图把回调放入微任务队列去执行,如果不支持,则检查是否支持setImmediate,不支持就用setTimeout。就是对环境进行一个降级处理,去执行flushCallbacks函数.

我们在实际开发中可以在callback放我们的代码,也可以await nexttick(),因nexttick返回的是一个promise 所以可以等nexttick执行完再执行我们的代码,这样也 可以获取到dom

源码分析参考itwangyang520:blog.csdn.net/itwangyang5… eat老虎滴兔兔:blog.csdn.net/clhdtzhh/ar…