nextTick 涉及的点,主要包含下面这些
1 | 1、任务队列callbacks |
Vue异步更新
vue实现dom更新是异步完成的,我们可以从下面这个例子中就能看的出
1 | <body> |
从上代码可以看出,我们改变了msg后,立马去输出h3标签的text值,发现还是原来的值。这就很明显了,vue的dom更新,并不是同步的,而是异步的,所以在输出时,实际dom还并没有更新。
原因:如果是同步的,当我们频繁的去改变状态值时,会频繁的导致我们的dom更新。
为什么用Vue.nextTick()
首先来了解一下JS的运行机制。
JS运行机制(Event Loop)
JS执行是单线程的,它是基于事件循环的。
所有同步任务都在主线程上执行,形成一个执行栈。
主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。
当执行栈中的所有同步任务执行完后,就会读取任务队列。那些对应的异步任务,会结束等待状态,进入执行栈。
主线程不断重复第三步。
这里主线程的执行过程就是一个tick
,而所有的异步结果都是通过任务队列来调度。Event Loop
分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入到一下tick
,并在两个**tick**
之间进行UI渲染。
由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()
方法。
什么是Vue.nextTick()
是Vue的核心方法之一,官方文档解释如下:
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
由于宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,再使用宏任务。
1 | // 空函数,可用作函数占位符 |
延迟调用优先级如下:
Promise
> MutationObserver
> setImmediate
> setTimeout
,前两个属于微任务,后两个属于宏任务。
Promise
:Promise
对象用于表示一个异步操作的最终完成 (或失败)及其结果值。MutationObserver
:MutationObserver
接口提供了监视对DOM树所做更改的能力。setImmediate
:setImmediate方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。setTimeout
:setTimeout方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。
1 | export function nextTick(cb? Function, ctx: Object) { |
next-tick.js
对外暴露了nextTick
这一个参数,所以每次调用Vue.nextTick
时会执行:
把传入的回调函数
cb
压入callbacks
数组执行
timerFunc
函数,延迟调用flushCallbacks
函数遍历执行
callbacks
数组中的所有函数
这里的 callbacks
没有直接在 nextTick
中执行回调函数的原因是保证在同一个 tick
内多次执行nextTick
,不会开启多个异步任务,而是把这些异步任务都压成一个同步任务,在下一个 tick
执行完毕。
总结
原理
将传递的回调函数用
try catch
包裹后然后放进callback数组。执行timerFunc函数,在浏览器的异步任务队列放入一个刷新callbacks数组的函数。
因为兼容问题,vue做了微任务向宏任务的降级方案
使用
- 在Vue生命周期的
created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中。
原因:是created()
钩子函数执行时DOM其实并未进行渲染。 - 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在
Vue.nextTick()
的回调函数中。
原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。