nextTick() 函数的实现

289 阅读2分钟

本节主要简述 nextTick() 函数

从响应式原理的派发通知的过程中,我们知道从数据的变化到 DOM 更新是一个异步的过程,nextTick 是怎么保证它是在下一个 tick 执行所有回调呢?

接下来分析 nextTick() 函数的实现,它的实现在一个单独的 JS 文件内,代码不足百行左右,我们首先看一该函数的定义,如下所示。


const callbacks = []
let pending = false
function nextTick (cb, ctx) {
  let _resolve
  // 将包含 cb 的匿名函数 push 到 callbacks 数组中
  // 没有 cb 则将 Promise.resolve 作为回调 push 数组中
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // pending 标志位确保执行一次 timerFunc()
  if (!pending) {
    pending = true
    timerFunc()
  }
  // cb 不存在,Promise 可用,则赋值 _resolve 相当于 Promise.resolve 函数
  // 返回一个 promise 实例,用 promise.then() 接收 _resolve() 执行的结果
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

由上述代码可知,在 nextTick() 函数内

  • 首先将回调函数 cb 被包裹在匿名函数内,然后将匿名函数 pushcallbacks 队列中。
  • 若没有接收回调函数 cb 参数,则将变量 _resolve 作为函数在匿名函数内执行,然后将匿名函数 pushcallbacks 队列中。由于 cb 不存在,所以变量 _resolve 被赋值为 Promise.resolve 函数,且 nextTick() 函数返回一个 Promise 实例,所以如:this.$nextTick().then()
  • 通过标志位 pending 确保即使多次执行 nextTick() 函数,也仅仅执行一次 timerFunc() 函数。所以接下来重点介绍 timerFunc() 函数的实现。

接下来分析 timerFunc() 函数的实现,此过程涉及 JS 的运行机制,可以自行百度哦!

呢么 timerFunc() 函数如何实现即使多次调用 nextTick() 函数, 但是也只会在下一个 tick 执行所有回调?

在该函数内部主要通过宏/微任务来实现,也可以暂时理解为通过延时函数实现的。具体代码如下:

  • 先查看 Promise 是否定义,若定义则为微任务 p.then()
  • 再看 setImmediate() 函数是否定义,非标准特性不建议使用,可以通过函数setTimeout(fn, 0) 来模仿该功能。
  • 直接使用 setTimeout(fn, 0) 函数来实现。
let timerFunc;
if (typeof Promise !== "undefined") {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
} else if (typeof setImmediate !== "undefined") {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

其实 timerFunc() 函数就是通过各种方法进行延迟执行 flushCallbacks() 函数。在该函数内首先浅复制了存储函数的数组 callbacks,然后重置 pending、callbacks 数据。最后遍历数组执行函数。代码如下:

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}