一、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的实现借助了promise
和MutationObserver
,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 运行方式后续补充。。。。