介绍:
nextTick()是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据后使用此方法来获取更新后的 DOM。
说明:
nextTick是在update更新的时候调用的,采用异步的方式更新DOM。当数据发生变化,Vue不会立刻更新DOM 而是开启一个队列把更新函数也就是Watcher放入队列中。当任务队列中同一个 Watcher 被多次触发,会避免重复只推入到队列中一次,然后刷新队列。接着在下一个事件循环中,nextTick()将这些更新函数以cb的形式放到任务队列的尾部,当事件循环结束且任务队列为空之后就会进行DOM渲染,DOM渲染完成后再去执行这些cb,也就拿到更新后的值。
原理:
update会调用queueWatcher(),queueWatcher()会把watcher实例添加到 queue,在添加之前做判重处理,会判断缓存对象has中是否已经存在该id,如果判断出 has[id] 不存在就push到queue,就不会每次改变数据都触发watcher。然后调用nextTick把消耗queue的flushSchedulerQueue 放到 callbacks队列,然后把消耗callbacks队列的flushCallback放入到下个事件循环当中,到下个事件循环会根据pending 来判断当前是否要执行timerFunc,若执行timerFunc则会调用flushCallback,timerFunc会根据当前环境判断使用哪种异步方式,优先使用依次是Promise(微),MutationObserver监视dom变动(微),setImmediate(宏),setTimeout(宏)。
【flushCallbacks函数的作用就是对所有callback进行遍历,然后指向响应的回调函数,也就是代码里写的nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。】
为什么要先把cb压入callbacks数组⽽不是直接在nextTick中执⾏cb?
起初将传入的回调函数cb(也就是flushSchedulerQueue)压入callbacks数组,最后通过timerFunc函数一次性解决。原因是保证能在同⼀个事件循环内多次执⾏nextTick,不会开启多个异步任务,⽽把这些异步任务都压成⼀个同步任务,在下⼀个事件循环执⾏完毕。
应用场景:
1.create中想要获取dom时;
2.响应式数据变化后获取dom更新后的值;
源码:
/* @flow */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
let pending = false
/**
* 对所有callback进行遍历,然后指向响应的回调函数
* 使用 callbacks 保证了可以在同一个tick内执行多次 nextTick,不会开启多个异步任务,而把这些异步任务都压成一个同步任务,在下一个 tick 执行完毕。
*/
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]( "i")
}
}
let timerFunc
/**
* timerFunc 实现的就是根据当前环境判断使用哪种方式实现
* 就是按照 Promise.then和 MutationObserver以及setImmediate的优先级来判断,支持哪个就用哪个,如果执行环境不支持,会采用setTimeout(fn, 0)代替;
*/
// 判断是否支持原生 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 不支持 Promise的话,再判断是否原生支持 MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 新建一个 textNode的DOM对象,使用 MutationObserver 绑定该DOM并传入回调函数,在DOM发生变化的时候会触发回调,该回调会进入主线程(比任务队列优先执行)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
// 此时便会触发回调
textNode.data = String(counter)
}
isUsingMicroTask = true
// 不支持的 MutationObserver 的话,再去判断是否原生支持 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Promise,MutationObserver, setImmediate 都不支持的话,最后使用 setTimeout(fun, 0)
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 该函数的作用就是延迟 cb 到当前调用栈执行完成之后执行
export function nextTick (cb?: Function, ctx?: Object) {
// 传入的回调函数会在callbacks中存起来
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pending是一个状态标记,保证timerFunc在下一个tick之前只执行一次
if (!pending) {
pending = true
/**
* timerFunc 实现的就是根据当前环境判断使用哪种方式实现
* 就是按照 Promise.then和 MutationObserver以及setImmediate的优先级来判断,支持哪个就用哪个,如果执行环境不支持,会采用setTimeout(fn, 0)代替;
*/
timerFunc()
}
// 当nextTick不传参数的时候,提供一个Promise化的调用
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}