异步更新队列指的是当状态发生变化时,Vue异步执行DOM更新。
项目开发场景:当我们将状态改变之后想获取更新后的DOM,往往我们获取到的DOM是更新前的旧DOM,我们需要使用vm.$nextTick方法异步获取DOM,例如:
Vue. component( 'example', { template :'{{ message }}', data:function() { return{ message :'没有更新'} }, methods :{ updateMessage:function() { this. message='更新完成'; console. log( this. $el. textContent) //=> '没有更新' this. $nextTick( function() { console. log( this. $el. textContent) //=> '更新完成' }) } }})
这样做很麻烦,但为什么Vue还要这样做呢?
首先我们假设Vue是同步执行DOM更新,会有什么问题?
如果同步更新DOM将会有这样一个问题,我们在代码中同步更新数据N次,DOM也会更新N次,伪代码如下:
this. message='更新完成'//DOM更新一次this. message='更新完成2'//DOM更新两次this. message='更新完成3'//DOM更新三次this. message='更新完成4'//DOM更新四次
但事实上,我们真正想要的其实只是最后一次更新而已,也就是说前三次DOM更新都是可以省略的,我们只需要等所有状态都修改好了之后再进行渲染就可以减少一些无用功。
而这种无用功在Vue2.0开始变得更为重要,Vue2.0开始引入了Virtualdom,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用VirtualDOM进行计算得出需要更新的具体的DOM节点,然后对DOM进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要。
组件内部使用VIrtualDOM进行渲染,也就是说,组件内部其实是不关心哪个状态发生了变化,它只需要计算一次就可以得知哪些节点需要更新。也就是说,如果更改了N个状态,其实只需要发送一个信号就可以将DOM更新到最新。例如:
this. message='更新完成'this. age=23this. name=berwin
代码中我们分三次修改了三种状态,但其实Vue只会渲染一次。因为VIrtualDOM只需要一次就可以将整个组件的DOM更新到最新,它根本不会关心这个更新的信号到底是从哪个具体的状态发出来的。
那如何才能将渲染操作推迟到所有状态都修改完毕呢?很简单,只需要将渲染操作推迟到本轮事件循环的最后或者下一轮事件循环。也就是说,只需要在本轮事件循环的最后,等前面更新状态的语句都执行完之后,执行一次渲染操作,它就可以无视前面各种更新状态的语法,无论前面写了多少条更新状态的语句,只在最后渲染一次就可以了。
将渲染推迟到本轮事件循环的最后执行渲染的时机会比推迟到下一轮快很多,所以Vue优先将渲染操作推迟到本轮事件循环的最后,如果执行环境不支持会降级到下一轮。
当然,Vue的变化侦测机制决定了它必然会在每次状态发生变化时都会发出渲染的信号,但Vue会在收到信号之后检查队列中是否已经存在这个任务,保证队列中不会有重复。如果队列中不存在则将渲染操作添加到队列中。
之后通过异步的方式延迟执行队列中的所有渲染的操作并清空队列,当 同一轮事件循环中反复修改状态时,并不会反复向队列中添加相同的渲染操作。
所以我们在使用Vue时,修改状态后更新DOM都是异步的。
说到这里简单介绍下什么是事件循环。
事件循环机制
JS中存在一个叫做执行栈的东西。JS的所有同步代码都在这里执行,当执行一个函数调用时,会创建一个新的执行环境并压到栈中开始执行函数中的代码,当函数中的代码执行完毕后将执行环境从栈中弹出,当栈空了,也就代表执行完毕。(小结:执行栈 ——对应——> 同步代码)
这里有一个问题是代码中不只是同步代码,也会有异步代码。当一个异步任务执行完毕后会将任务添加到任务队列中。例如:
setTimeout( _=>{}, 1000)
代码中setTimeout会在一秒后将回调函数添加到任务队列中。事实上异步队列也分两种类型:微任务、宏任务。(小结:任务队列 ——对应——> 异步代码)
微任务和宏任务的区别是,当执行栈空了,会检查微任务队列中是否有任务,将微任务队列中的任务依次拿出来执行一遍。当微任务队列空了,从宏任务队列中拿出来一个任务去执行,执行完毕后检查微任务队列,微任务队列空了之后再从宏任务队列中拿出来一个任务执行。这样持续的交替执行任务叫做事件循环,即上文所说的同一轮事件循环。
属于微任务(microtask)的事件有以下几种:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
属于宏任务(macrotask)的事件有以下几种:
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI交互事件