Vue的特点之一就是响应式,但是有时候数据更新了,我们看到页面上的DOM并没有立即更新。如果我们需要在DOM更新后执行一段代码,可以使用nextTick来实现。让我们看一个示例exportdefault{data(){return{msg:0}},mounted(){this.msg=1this.msg=2this.msg=3},watch:{msg(){console.log(this.msg)}}}这里的结果是只输出一个3,而不是依次输出1、2、3。为什么是这样?Vue的官方文档解释说,Vue是异步执行DOM更新的。只要观察到数据变化,Vue就会启动一个队列,并缓冲在同一个事件循环中发生的所有数据变化。如果同一个观察者被多次触发,它只会被推入队列一次。这种缓冲时的重复数据删除对于避免不必要的计算和DOM操作很重要。然后,在下一个事件循环“tick”中,Vue刷新队列并执行实际(去重)工作。Vue内部尝试使用原生的Promise.then和MessageChannel来实现异步队列。如果执行环境不支持,它将使用setTimeout(fn,0)代替。如果出现这种情况,挂载的钩子函数的下一个变量a的值将被++循环执行1000次。每次++都会根据响应快慢触发setter->Dep->Watcher->update->run。如果此时不异步更新视图,每次++都会直接操作DOM,非常耗性能。因此,Vue实现了一个队列,在下一个Tick(或者当前Tick的microtask阶段),统一执行队列中Watcher的运行。同时,相同id的Watcher不会重复加入队列,所以1000次Watcher运行不会被执行。最后的结果是直接把a的值从1改成1000,性能大大提升。在vue中,数据监控是通过重写Object.defineProperty内部的set和get方法来实现的。Vue异步更新DOM。每当观察到数据变化时,vue都会启动一个队列,并把同一个事件循环中所有的数据变化都缓存起来,当下一次事件循环发生时,队列会被清空,DOM也会被更新。要理解vue.nextTick的执行机制,我们先来理解javascript的事件循环。js事件循环js任务队列分为同步任务和异步任务,所有同步任务都在主线程中执行。异步任务可能在macrotask或microtask中,异步任务进入EventTable和register函数。当指定的事情完成后,EventTable会把这个函数移到EventQueue中。主线程中的任务执行完后为空,会去EventQueue中读取对应的函数,进入主线程执行。上述过程会不断重复,这就是常说的事件循环(eventloop)。macro-task(宏任务):执行栈每次执行的代码都是宏任务(包括每次从事件队列中获取事件回调,放入执行栈执行)。为了让js内部的(宏)任务和DOM任务能够有序执行,浏览器会在一个(宏)任务执行结束后,下一个(宏)任务开始执行前重新渲染页面。宏任务主要包括:脚本(整体代码)setTimeout/setIntervalsetImmediate(Node.js环境)I/OUIrenderpostMessageMes??sageChannel微任务(micro-task):可以理解为当前任务执行后立即执行的任务结束。也就是说,在当前任务之后、下一个任务之前和渲染之前。所以它的响应速度会比setTimeout快(setTimeout是一个任务),因为不需要等待渲染。也就是说,一个macrotask执行完之后,所有在它执行过程中产生的microtask都会被执行(渲染之前)。microtask主要包括:process.nextTick(Node.js环境)PromiseAsync/AwaitMutationObserver(html5新特性)队列(microtask)的主线程执行完后执行microqueue(微任务),microtask(微任务)执行队列中的一个任务宏队列(macrotask)执行一次,执行完后执行微任务(microtask),执行完依次循环。..Vue.nextTick源码Vue使用双向数据绑定来驱动数据更新。虽然这样可以避免直接操作DOM,提高性能,但是有时候我们不可避免的需要操作DOM。这时候,Vue.nextTick(callback)出现了,它接受一个回调函数,这个回调函数会在DOM更新完成后被调用。无论是vue.nextTick还是vue.prototype.\$nextTick都是直接使用nextTick的闭包函数。exportconstnextTick=(function(){constcallbacks=[]letpending=falselettimerFuncfunctionnextTickHandler(){pending=falseconstcopies=callbacks.slice(0)callbacks.length=0for(leti=0;i
