VUE-nextTick原理1.JS事件循环在介绍Vue的nextTick之前,先简单介绍一下JS的运行机制:JS执行是单线程的,是基于事件循环的。对于事件循环的理解,阮老师写了一篇文章说的很清楚,大致分为以下几个步骤:(1)所有的同步任务都在主线程上执行,形成一个执行上下文栈。(2)除了主线程之外,还有一个“任务队列”。只要异步任务有运行结果,就会在“任务队列”中放入一个事件。(3)一旦“执行栈”中的所有同步任务都执行完毕,系统就会去读取“任务队列”,看看里面有什么事件。那些对应的异步任务结束等待状态,进入执行栈,开始执行。(4)主线程不断重复上面的第三步。主线程的执行过程是一个tick,所有的异步结果都是通过“任务队列”来调度的。每个任务都存储在消息队列中。规范规定任务分为宏任务和微任务两类,每个宏任务结束后,必须清除所有微任务。在浏览器环境下,常见的宏任务有setTimeout、MessageChannel、postMessage、setImmediate;常见的微任务包括MutationObsever和Promise.then。2.Vue的nextTickVue的nextTick顾名思义就是下一个tick。Vue在内部实现了nextTick并将其作为全局API公开。支持传入回调函数,保证回调函数在下一个tick执行。官网文档介绍了Vue.nextTick的使用场景:用法:推迟下一个DOM更新周期后执行的回调。更改一些数据后立即使用它以等待DOM更新。使用:在下一个DOM更新周期结束后执行延时回调,修改数据后立即使用该方法获取更新后的DOM。在Vue.js中,数据驱动的视图发生了变化。由于JS执行是单线程的,一个tick过程中可能会多次修改数据,但是Vue.js不会傻到每次修改数据都去驱动。一旦视图发生变化,它会将所有的数据变化推送到一个队列中,然后在内部调用nextTick来更新视图,所以数据到DOM视图的变化需要在下一个tick完成。3、源码1。/*@flow*/2./*globalsMessageChannel*/4.import{noop}from'shared/util'5.import{handleError}from'./error'6.import{isIOS,isNative}from'./env'8.constcallbacks=[]9.letpending=false11.functionflushCallbacks(){12.pending=false13.constcopies=callbacks.slice(0)14.callbacks.length=015.for(leti=0;i{39.setImmediate(flushCallbacks)40.}41.}elseif(typeofMessageChannel!=='undefined'&&(42.isNative(MessageChannel)||43.//PhantomJS44.MessageChannel.toString()==='[objectMessageChannelConstructor]'45.)){46.constchannel=newMessageChannel()47.constport=channel.port248.channel.port1.onmessage=flushCallbacks49.macroTimerFunc=()=>{50.port.postMessage(1)51.}52.}else{53./*istanbul忽略下一个*/54.macroTimerFunc=()=>{55.setTimeout(flushCallbacks,0)56.}57.}59.//确定MicroTask延迟实现。60./*istanbulignorenext,$flow-disable-line*/61.if(typeofPromise!=='undefined'&&isNative(Promise)){62.constp=Promise.resolve()63.microTimerFunc=()=>{64.p.then(flushCallbacks)65.//在有问题的UIWebViews中,Promise.then并没有完全中断,但是66.//它可能会陷入一种奇怪的状态,回调被推入67.//微任务队列,但队列不会被刷新,直到浏览器68.//需要做一些其他工作,例如处理一个定时器。因此我们可以69.//通过添加一个空计时器来“强制”刷新微任务队列。70.if(isIOS)setTimeout(noop)71.}72.}else{73.//回退到宏74.microTimerFunc=macroTimerFunc75.}77./**78.*包装一个函数,这样如果里面有任何代码触发状态更改,79。*使用任务而不是MicroTask对更改进行排队。80.*/81.导出函数withMacroTask(fn:Function):Function{82.returnfn._withTask||(fn._withTask=function(){83.useMacroTask=true84.constres=fn.apply(null,arguments)85.useMacroTask=false86.returnres87.})88.}90.exportfunctionnextTick(cb?:函数,ctx?:对象){91.让_resolve92.回调。push(()=>{93.if(cb){94.try{95.cb.call(ctx)96.}catch(e){97.handleError(e,ctx,'nextTick')98.}99.}elseif(_resolve){100._resolve(ctx)101.}102.})103.if(!pending){104.pending=true105.if(useMacroTask){106.macroTimerFunc()107.}else{108.microTimerFunc()109.}110.}111.//$flow-disable-line112.if(!cb&&typeofPromise!=='undefined'){113.returnnewPromise(resolve=>{114._resolve=resolve115.})116.}117.}这个源码中的next-tick.js文件里面有一个重要的注释,这里翻译一下:在vue2.5之前,nextTick基本都是基于micro实现的任务,但在某些情况下微任务的优先级太高,并且可能在连续的顺序事件之间(例如#4521,#6690)甚至在冒泡过程之间的同一事件触发(#6566)但是如果全部改成macrotask,对一些有重绘和动画的场景也会有性能影响,比如issue#6813。vue2.5以后的版本提供的解决方案是默认使用微任务,但在需要的时候强制使用宏任务(比如在v-on附带的事件处理器中)。这个强制是指Vue.js在绑定DOM事件的时候,默认会对回调handler函数调用withMacroTask方法handler=withMacroTask(handler)做一层包装,保证在整个回调函数执行过程中,任何当数据状态发生变化时,这些变化会被推送到宏任务中。对于宏任务的执行,Vue.js首先检查是否支持原生的setImmediate,这是只有高版本的IE和Edge才支持的特性。如果不支持,会检查是否支持原生的MessageChannel。如果不支持,则会降级为setTimeout0。4.一个小例子{{name}}