Vue源码之旅(2)-nextTick

Author Avatar
张帅 12月 09, 2017

基于vue version2.1.4


Vue.nextTick( [callback, context] )

Vue 官方解释用法

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

#开发者调用后执行过程

开发者调用后执行过程
timerFunc是在Vue源码初始化后就已经确定的。

#Vue内部初始化timerFunc执行过程

Vue内部初始化timerFunc执行过程
nextTick的实现基于浏览器的microtask队列。浏览器优化了dom的更新,将dom更新划分为宏任务和微任务,js操作dom后并不是立即渲染,而是进入微任务队列中,以队列的形式统一进行更新。所以vue就是基于这个特性,实现了dom循环结束的监听,以便开发者做进一步操作。

但是并不是所有浏览器都暴露出了微任务执行结束事件,所以vue通过功能检查,根据各个浏览器处理能力的不同分出了Promise.then, MutationObserver, setTimeout这一步步的降级方案。

以下是各种处理的源码说明(只截取了部分片段)

Promsie.then
1
2
3
4
5
6
7
8
9
10
11
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// UIWebViews, Promise.then并没有完全按规范实现
// 回调导入了微任务队列,但是DOM并没有刷新
// 所有通过空定时器hack,手动强制刷新了下
if (isIOS) setTimeout(noop)
}
}
MutationObserver

MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力.该API设计用来替换掉在DOM3事件规范中引入的Mutation事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
// 这里巧妙的用了textNode在0,1之间变换,
// 来进行dom变化后的监听
counter = (counter + 1) % 2
textNode.data = String(counter)
}
}
setTimeout

微任务没实现,只能用宏任务代替了

1
2
3
4
5
6
7
else {
// fallback to setTimeout
/* istanbul ignore next */
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}

附录