知食记
搜索文档…
nextTick

功能

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

用法

1
// 修改数据
2
vm.msg = 'Hello';
3
// DOM 还没有更新
4
Vue.nextTick(function() {
5
// DOM 更新了
6
});
7
8
// 作为一个 Promise 使用
9
Vue.nextTick().then(function() {
10
// DOM 更新了
11
});
Copied!

能力检测

按照优先级检测
    1.
    native promise
    2.
    MutationObserver(非ie)
    3.
    setImmediate
    4.
    setTimeout
官方文档注释中,MutationObserver有更广的适配,但是ie11和UIWebView in iOS >= 9.3.3有严重的bug,所以native promise是第一优先级的
1
// The nextTick behavior leverages the microtask queue, which can be accessed
2
// via either native Promise.then or MutationObserver.
3
// MutationObserver has wider support, however it is seriously bugged in
4
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
5
// completely stops working after triggering a few times... so, if native
6
// Promise is available, we will use it:
7
/* istanbul ignore next, $flow-disable-line */
8
if (typeof Promise !== 'undefined' && isNative(Promise)) {
9
const p = Promise.resolve()
10
timerFunc = () => {
11
p.then(flushCallbacks)
12
// In problematic UIWebViews, Promise.then doesn't completely break, but
13
// it can get stuck in a weird state where callbacks are pushed into the
14
// microtask queue but the queue isn't being flushed, until the browser
15
// needs to do some other work, e.g. handle a timer. Therefore we can
16
// "force" the microtask queue to be flushed by adding an empty timer.
17
if (isIOS) setTimeout(noop)
18
}
19
isUsingMicroTask = true
20
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
21
isNative(MutationObserver) ||
22
// PhantomJS and iOS 7.x
23
MutationObserver.toString() === '[object MutationObserverConstructor]'
24
)) {
25
// Use MutationObserver where native Promise is not available,
26
// e.g. PhantomJS, iOS7, Android 4.4
27
// (#6466 MutationObserver is unreliable in IE11)
28
let counter = 1
29
const observer = new MutationObserver(flushCallbacks)
30
const textNode = document.createTextNode(String(counter))
31
observer.observe(textNode, {
32
characterData: true
33
})
34
timerFunc = () => {
35
counter = (counter + 1) % 2
36
textNode.data = String(counter)
37
}
38
isUsingMicroTask = true
39
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
40
// Fallback to setImmediate.
41
// Technically it leverages the (macro) task queue,
42
// but it is still a better choice than setTimeout.
43
timerFunc = () => {
44
setImmediate(flushCallbacks)
45
}
46
} else {
47
// Fallback to setTimeout.
48
timerFunc = () => {
49
setTimeout(flushCallbacks, 0)
50
}
51
}
Copied!

回调队列执行

总体流程就是,接收回调函数,将回调函数推入回调函数队列中。
同时,在接收第一个回调函数时,执行能力检测中对应的异步方法(异步方法中调用了回调函数队列)。
如何保证只在接收第一个回调函数时执行异步方法?
nextTick源码中使用了一个异步锁的概念,即接收第一个回调函数时,先关上锁,执行异步方法。此时,浏览器处于等待执行完同步代码就执行异步代码的情况。
打个比喻:相当于一群旅客准备上车,当第一个旅客上车的时候,车开始发动,准备出发,等到所有旅客都上车后,就可以正式开车了。
当然执行flushCallbacks函数时有个难以理解的点,即:为什么需要备份回调函数队列?执行的也是备份的回调函数队列?
因为,会出现这么一种情况:nextTick套用nextTick。如果flushCallbacks不做特殊处理,直接循环执行回调函数,会导致里面nextTick中的回调函数会进入回调队列。这就相当于,下一个班车的旅客上了上一个班车。
1
// 回调函数队列
2
const callbacks = []
3
// 异步锁
4
let pending = false
5
6
// 执行回调函数
7
function flushCallbacks () {
8
// 重置异步锁
9
pending = false
10
// 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
11
const copies = callbacks.slice(0)
12
callbacks.length = 0
13
// 执行回调函数队列
14
for (let i = 0; i < copies.length; i++) {
15
copies[i]()
16
}
17
}
18
19
...
20
21
// 我们调用的nextTick函数
22
export function nextTick (cb?: Function, ctx?: Object) {
23
let _resolve
24
// 将回调函数推入回调队列
25
callbacks.push(() => {
26
if (cb) {
27
try {
28
cb.call(ctx)
29
} catch (e) {
30
handleError(e, ctx, 'nextTick')
31
}
32
} else if (_resolve) {
33
_resolve(ctx)
34
}
35
})
36
// 如果异步锁未锁上,锁上异步锁,调用异步函数,准备等同步函数执行完后,就开始执行回调函数队列
37
if (!pending) {
38
pending = true
39
timerFunc()
40
}
41
// $flow-disable-line
42
// 2.1.0新增,如果没有提供回调,并且支持Promise,返回一个Promise
43
if (!cb && typeof Promise !== 'undefined') {
44
return new Promise(resolve => {
45
_resolve = resolve
46
})
47
}
48
}
Copied!

MutationObserver

MutationObserver 如何模拟 nextTick 这点,其实就是创建一个 TextNode 并监听内容变化,然后要 nextTick 的时候去改一下这个节点的文本内容:
1
const observer = new MutationObserver(flushCallbacks)
2
const textNode = document.createTextNode(String(counter))
3
observer.observe(textNode, {
4
characterData: true
5
})
6
timerFunc = () => {
7
counter = (counter + 1) % 2
8
textNode.data = String(counter)
9
}
Copied!
最近更新 1yr ago