在 Vue.js 中,当我们修改了数据或调用了某个方法后,Vue.js 会异步地更新 DOM。这就意味着,如果我们想要在 DOM 更新后执行一些操作,比如获取 DOM 元素的高度或宽度,就需要等到 DOM 更新完成后再执行这些操作。
官方文档:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
使用方法及使用场景
1. 在 Vue 生命周期的 created钩子函数进行 DOM 操作一定要放到 $nextTick回调函数中。
在created 钩子函数执行的时候 DOM 其实并未进行任何渲染,而此时进行 DOM操作无异于徒劳,所以此处一定要将 DOM操作的 js 代码放进 $extTick()的回调函数中。
与之对应的就是 mounted钩子函数,因为该钩子执行时所有的 DOM挂载和渲染都已完成,此时在该钩子函数中进行任何 DOM 操作都不会有问题。
示例代码
2. 调用一个方法 改变数据来更新DOM,如果立即在该方法后面读取DOM,则可能会得到一个旧的值,因为DOM还没有更新。在这种情况下,可以使用$nextTick来确保来拿到更新后DOM的值
示例代码
我们发现 只要当我们在给age赋值后立即查看数据时,都会出现问题。也就是说修改数据不会立即更新视图。
原理解读
那么 到底是如何实现的呢?其实, 的原理很简单,它是通过 将回调函数添加到一个队列中,等到所有同步任务执行完成后再执行这个队列中的回调函数。这个队列就是 Vue.js 内部维护的一个异步队列,可以通过 方法将回调函数添加到队列中。
-
当数据数据发生变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。
-
如果同一个 watcher 被多次触发,只会被推入到队列中一次。
-
然后,在下一个的事件循环“tick”中,Vue 执行队列里的所有回调。
Vue在内部对异步队列尝试使用原生的 Promise.then
、MutationObserver
和 setImmediate
,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。
这一部分的源码如下:
-
首先,先来看 nextTick 函数,该函数的主要逻辑是:把传入的回调函数 cb 推入 回调队列 callback 数组。
-
然后利用pending 进行上锁,同一事件循环中调用多次nextTick , timerFn 只会执行一次,执行timerFun 方法。timerFunc 会根据当前环境是否支持微任务和宏任务在下一个tick中调用 flushCallback 方法。
-
flushCallback 方法比较简单,就是把队列里的方法都执行一遍。
nextTick 函数最后还有一段逻辑:
这是当 nextTick 不传 cb 参数的时候,提供一个 Promise 化的调用,比如:
当 _reslove 函数执行,就会跳到 .then 的逻辑中。
总结
以上就是对 nextTick 的源码分析,我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行。