NextTick 原理分析

nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。

在 Vue 2.4 之前都是使用的 microtasks,但是 microtasks 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都使用 macrotasks 又可能会出现渲染的性能问题。所以在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on。

对于实现 macrotasks ,会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel ,以上都不行的话就使用 setTimeout

  1. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  2. macroTimerFunc = () => {
  3. setImmediate(flushCallbacks)
  4. }
  5. } else if (
  6. typeof MessageChannel !== 'undefined' &&
  7. (isNative(MessageChannel) ||
  8. // PhantomJS
  9. MessageChannel.toString() === '[object MessageChannelConstructor]')
  10. ) {
  11. const channel = new MessageChannel()
  12. const port = channel.port2
  13. channel.port1.onmessage = flushCallbacks
  14. macroTimerFunc = () => {
  15. port.postMessage(1)
  16. }
  17. } else {
  18. /* istanbul ignore next */
  19. macroTimerFunc = () => {
  20. setTimeout(flushCallbacks, 0)
  21. }
  22. }

nextTick 同时也支持 Promise 的使用,会判断是否实现了 Promise

  1. export function nextTick(cb?: Function, ctx?: Object) {
  2. let _resolve
  3. // 将回调函数整合进一个数组中
  4. callbacks.push(() => {
  5. if (cb) {
  6. try {
  7. cb.call(ctx)
  8. } catch (e) {
  9. handleError(e, ctx, 'nextTick')
  10. }
  11. } else if (_resolve) {
  12. _resolve(ctx)
  13. }
  14. })
  15. if (!pending) {
  16. pending = true
  17. if (useMacroTask) {
  18. macroTimerFunc()
  19. } else {
  20. microTimerFunc()
  21. }
  22. }
  23. // 判断是否可以使用 Promise
  24. // 可以的话给 _resolve 赋值
  25. // 这样回调函数就能以 promise 的方式调用
  26. if (!cb && typeof Promise !== 'undefined') {
  27. return new Promise(resolve => {
  28. _resolve = resolve
  29. })
  30. }
  31. }