这是我参与更文挑战的第13天,活动详情查看: 更文挑战。
前言
前面,我们已经大致梳理 Vue.js 里的整体流程和基本原理,接下来我们还会探究 Vue.js 实例或组件上的一些方法的原理和具体实现。
本次,我们将一起来探索 nextTick
。
vm.$nextTick
nextTick
接收一个回调函数作为参数,它的作用是将回调延迟到下次DOM更新周期之后执行。它与全局方法Vue.nextTick
一样,不同的是回调的this自动绑定到调用它的实例上。如果没有提供回调且在支持Promise的环境中,则返回一个 Promise
。
应用场景
我们在开发项目时会遇到一种场景:当更新了状态(数据)后,需要对新 DOM 做一些操作,但是这时我们其实获取不到更新后的DOM,因为还没有重新渲染。这个时候我们需要使用 nextTick 方法。
new Vue({
methods: {
example: function() {
this.message = 'changed';
this.$nextTick(function(){
// Dom 更新了
// this 指向当前实例
this.doSomethingElse()
})
}
}
})
复制代码
在Vue.js中,当状态发生变化时,watcher
会得到通知,然后触发虚拟DOM的渲染流程。而 watcher 触发渲染这个操作并不是同步的,而是异步的。Vue.js 中有一个队列,每当需要渲染时,会将 watcher 推送到这个队列中,在下一次事件循环中再让 watcher 触发渲染的流程。
为什么Vue.js使用异步更新队列
我们之前学过,Vue.js 2.0 引入了虚拟DOM,变化侦测的通知只会发给组件。那么,如果我们同时修改了组件的两个状态,是不是组件就要重新渲染两次?这显示是不合理的。
如何解决重复渲染的问题?
我们只要把 watcher 实例添加到一个队列中缓存起来,并且在添加到队列时检查是否已经添加过了,只有不存在时,才将 watcher 实例添加到队列中。在下一次事件循环中,就可以依次触发 watcher 来重新渲染了。这样一来,在一个事件循环中,某个组件的有多个状态发生变化,这个组件也只会重新渲染 1 次。
异步任务
为了实现延迟运行的效果,我们需要使用到异步任务。Javascript 中的异步任务分为两种类型:微任务
和 宏任务
。
属于微任务的事件包括但不限于以下几种:
- Promise.then()
- MutationObserver
- Object.observe
- process.nextTick
属于宏任务的事件包括但不限于以下几种:
- setTimeout
- setInterval
- settImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI 交互事件
什么是执行栈
当我们执行一个方法时,JavaScript会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数、私有作用域中定义的变量以及this对象。这个执行环境会被添加到一个栈中,这个栈就是执行栈。
回到前面的问题,“下次DOM更新周期” 的意思其实是下次微任务执行时更新DOM。而 vm.$nextTick
其实是将回调添加到微任务中。只有在特殊情况下才会降级成宏任务,默认会添加到微任务中。
注意 不论是更新DOM的回调还是使用 vm.$nextTick
注册的回调,都是向微任务队列中添加任务,所以哪个任务先添加到队列中,就先执行哪个任务。