# vue的异步更新原理
我们把主线程执行一次的过程叫一个tick
,所以nextTick
就是下一个tick
的意思,也就是说用nextTick
的场景就是我们想在下一个tick
做一些事的时候。
nextTick
接收一个回调函数作为参数,它的作用是将回调延迟到下次DOM
更新周期之后执行。在vue.js中当状态发生变化时watcher
会得到通知,然后触发虚拟DOM
的渲染流程。而watcher
触发渲染这个操作并不是同步的,而是异步的。vue中有个队列,每当需要渲染时会将watcher
推送到这个队列中,在下一次事件循环中再让watcher
触发渲染的流程。
# 为什么使用异步更新队列
vue2.0使用虚拟DOM
进行渲染,变化侦测的通知只发送到组件,组件用到的所有状态的变化都会通知到同一个watcher
,然后虚拟DOM
会对整个组件进行对比diff
并更改DOM
。也就是说如果在同一轮事件循环中有两个数据发生变化,那么组件的watcher
会收到两份通知,从而进行两次渲染。事实上,只需要等所有状态都修改完毕。一次性将整个组件的DOM
渲染到最新即可。
要解决这个问题vue的实现方式就是将接收到通知的
watcher
实例添加到队列中缓存起来,并且在添加到队列之前检查其中是否已经存在相同的watcher
,只有不存在时才加入队列。然后在下一次事件循环中,vue会让队列中的watcher
触发渲染流程并清空队列。
下次DOM
更新周期的意思其实是下次微任务执行时更新DOM
。而vm.$nextTick
其实是将回调添加到微任务中。只有在特殊情况下才会降级为宏任务。
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for(let i=0;i<copies.length;i++) {
copies[i]()
}
}
let microTimerFunc
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
}
export function nextTick(cb,ctx) {
callbacks.push(() => {
if(cb) {
cb.call(ctx)
}
})
if(!pending) {
pending = true
microTimerFunc()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
TIP
我们通过数组
callbacks
来存储用户注册的回调,声明了变量pending
来标记是否已经向任务队列中添加任务。每当向任务队列中插入任务时,将pengding
设置为true
,每当任务被执行时将pending
设置为false
,这样就可以通过pending
的值来判断是否需要向任务队列中添加任务。flushCallbacks
,就是我们所说的被注册的那个任务。当这个函数被触发时,会将callbacks
中的所有函数依次执行。然后清空callbacks
,并将pending
设置为false
。也就是说,一轮事件循环中flushCallbacks
只会执行一次。优先检测是否原生⽀持
Promise
,不⽀持的话再去检测是否⽀持MutationObserver
,如果都不行就只能尝试宏任务实现,vue宏任务优先使用setImmediate
(只兼容IE)。如果都不支持就使用setTimeout
来将回调添加到宏任务队列中。
// 使用 mutationObserver
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
2
3
4
5
6
7
8
9
10
11
12
这段代码的作用是创建一个
MutationObserver
对象,通过观察一个文本节点的变化来触发回调函数flushCallbacks
。具体来说,它会创建一个文本节点,并将其添加到DOM
中。然后,MutationObserver
对象会监听这个文本节点的变化,一旦发生变化就会触发flushCallbacks
回调函数。在timerFunc
函数中,它会修改counter
变量的值,并将新的值设置为文本节点的data
属性,从而触发MutationObserver
对象的回调函数。这样就实现了一个异步更新的效果。