Bootstrap

vue2/vue3 中nextTick的作用和源码解读

一、Vue2 的nextTick作用和源码解读

官网解释:

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

修改Vue中的响应数据后触发界面更新这时一个异步的过程,使用同步操作是无法获取到正确的DOM结构/状态

源码

// 定义了一个全局变量 isUsingMicroTask 用于标识是否正在使用微任务
let isUsingMicroTask = false
// 创建了一个空数组 callbacks 用于存储待执行的回调函数。
const callbacks = []
//表示是否有待执行的回调函数
let pending = false

// 用于执行存储在 callbacks 数组中的回调函数
function flushCallbacks() {
  pending = false
  // 复制一份 callbacks 数组以防止在执行过程中数组被修改
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

接下来选择合适的定时器函数 timerFunc,用于执行 flushCallbacks

let timerFunc
// 检查环境中是否支持 Promise,并且该 Promise 是原生实现的 (微队列)
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 使用 Promise.resolve().then() 的方式来执行 flushCallbacks 函数
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
  isUsingMicroTask = true
} 
//如果不支持 Promise,但支持 MutationObserver,(微队列)
else if (
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
 	
  let counter = 1
  // 创建一个 MutationObserver 对象
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  // 以文本节点作为观察目标
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
  // 文本节点的内容发生变化时触发 flushCallbacks 函数。
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // 如果不支持 Promise 和 MutationObserver,但支持 setImmediate,则使用 setImmediate 函数来触发 flushCallbacks 函数
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
 // 如果以上都不支持,则回退到使用 setTimeout 来触发 flushCallbacks 函数。
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

可以看到Vue2的nextTick的实现借助了promiseMutationObserver,setImmediate, setTimeout

MutationObserver: 用于监控DOM(文档对象模型)的变化。当观察的DOM元素或其子元素发生变化时,它会触发一个回调函数

setImmediate: 是一个用于在 Node.js 中执行异步操作的函数。它类似于 setTimeout,但是会在当前事件循环的末尾立即执行回调函数,而不是等待一定的延迟时间

最后,定义了 nextTick 函数,用于添加回调函数到 callbacks 数组中

function nextTick(cd, ctx) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e: any) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 是否有待执行的任务来决定是否调用 timerFunc 来触发执行
  if (!pending) {
  	// 如果存在回调函数,则将 pending 置为 true,然后调用 timerFunc。
    pending = true
    timerFunc()
  }
	// 如果不传入回调函数,则返回一个 Promise 对象,在回调函数执行完成后,Promise 被 resolve。
   if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

二、Vue3 的nextTick作用和源码解读

官网解释:

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

vue3中简化了实现并保持了一致性,弃用了MutationObserver,setImmediate, setTimeout,专门采用了Promise来实现nextTick,减少了代码的复杂性,保证在大多数现代浏览器中可正常使用

源码

// runtime-core/scheduler.ts

// 创建了一个已解决的 Promise。通常用作基础 Promise,以便在其上链式调用异步操作
const resolvedPromise = Promise.resolve()

// 声明了一个变量 currentFlushPromise,初始值为 null。
// 这个变量用于追踪当前的 Promise 状态
let currentFlushPromise = null

function nextTick(this,fn) {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

currentFlushPromise的作用:控制和管理 nextTick 函数中回调函数的执行时机,以及确保回调函数按照正确的顺序执行。

currentFlushPromise 运行方式后续补充。。。。

;