核心实现分析
- 基础变量:
// 创建一个已解决的 Promise 实例作为基础 Promise
const resolvedPromise = /*@__PURE__*/ Promise.resolve() as Promise<any>
// 当前刷新队列的 Promise,初始为 null
let currentFlushPromise: Promise<void> | null = null
- nextTick 函数签名:
export function nextTick<T = void, R = void>(
this: T, // this 上下文类型
fn?: (this: T) => R, // 可选的回调函数
): Promise<Awaited<R>> // 返回 Promise {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
工作原理
- Promise 选择
-
函数首先选择使用 currentFlushPromise 或 resolvedPromise
-
如果当前有正在进行的更新队列(currentFlushPromise 存在),则使用它
-
否则使用基础的 resolvedPromise
为什么在 nextTick 中基础 Promise 选择使用 resolvedPromise 而不是 new Promise()
1.性能考虑
Prmise.resolve() 会立即创建一个已经解决(resolved)的 Promise 对象,不需要等待异步操作完成。相比之下,使用 new Promise() 需要创建一个新的 Promise 实例并执行 executor 函数,这会带来额外的性能开销。
2.简化微任务调度
Promise.resolve() 创建的是一个已完成状态的 Promise,可以直接用于调度微任务
在 nextTick 实现中,我们只需要一个简单的机制来将任务推入微任务队列
不需要显式的 resolve/reject 处理
- 确定性
使用 Promise.resolve() 可以确保得到一个处于 fulfilled 状态的 Promise
这样可以保证调度器中的任务按预期执行,不会出现意外的 pending 状态
- 代码复用
const p = currentFlushPromise || resolvedPromise
- 这个已解决的 Promise 可以被重复使用
- 避免了每次调用 nextTick 都创建新的 Promise 实例
- 回调处理
// 如果提供了回调函数
return fn ? p.then(this ? fn.bind(this) : fn) : p
-
如果传入回调函数
fn:-
检查是否有 this 上下文
-
如有,则绑定 this 上下文到回调函数
-
将回调添加到 Promise 链中
-
-
如果没有回调,直接返回 Promise
使用场景
- 无回调使用:
await nextTick()
// 等待下一个 DOM 更新周期
- 带回调使用:
nextTick(() => {
// DOM 更新后执行的代码
})
重要特点
- 微任务调度:
-
使用 Promise 实现,确保在当前宏任务结束后执行
-
属于微任务队列,优先级高于 setTimeout 等宏任务
- 更新同步:
-
如果当前有正在进行的更新(currentFlushPromise),会等待该更新完成
-
确保回调在所有状态更新和 DOM 更新后执行
- 灵活性:
-
支持 Promise 方式和回调方式两种使用方法
-
保持了 this 上下文的正确绑定