本节主要简述 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被包裹在匿名函数内,然后将匿名函数push到callbacks队列中。 - 若没有接收回调函数
cb参数,则将变量_resolve作为函数在匿名函数内执行,然后将匿名函数push到callbacks队列中。由于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]()
}
}