在开始之前,我们先看看它的官方定义:在下一个DOM更新周期结束后执行延迟回调。修改数据后立即使用此方法。获取更新的DOM后是否有一堆问号?让我们找出产生问号的关键字。下一个DOM更新周期结束后?执行延迟回调?更新了DOM?从以上三个问题,大胆猜测一下。vue有更新DOM的策略,不是同步更新nextTick可以接收一个函数输入nextTick作为参数后,可以获取到最新的数据,问题就抛出来了。让我们看看如何使用import{createApp,nextTick}from'vue'constapp=createApp({setup(){constmessage=ref('Hello!')constchangeMes??sage=asyncnewMessage=>{message.value=newMessage//这里从DOM获取的值是旧值awaitnextTick()//从DOM获取nextTick后的值是更新后的值console.log('现在DOM更新了')}}})自己试试那么nextTick是怎么做到的呢?为了更好的理解后面的内容,这里不得不从js的执行机制说起。我们都知道JS是单线程语言,也就是说一次只能做一件事。可能有同学会问,为什么JS不能多线程呢?多线程可以同时做多件事。是否多线程取决于语言的用途。一个很简单的例子,如果一个添加DOM,一个同时删除DOM,此时语言不知道该做什么。应该加也可以删,所以从应用场景来看,JS只能单线程单线程,也就是说我们所有的任务都需要排队,后面的任务要等前面的任务完成在他们被执行之前。如果前面的任务耗时很长,一些在用户看来不需要等待的任务就会永远等待下去。这从体验的角度来说是不能接受的,所以在JS中出现了异步概念的概念。一个任务执行完后,可以异步执行下一个任务,不进入主线程,而是进入“任务队列”(taskqueue)。只有当“任务队列”通知主线程有异步任务可以执行时,该任务才会进入主线程执行运行机制(1)所有同步任务都在主线程上执行,形成一个执行上下文栈(2)除了主线程之外,还有一个“任务队列”。只要异步任务有运行结果,就会在“任务队列”中放入一个事件。(3)一旦“执行栈”中的所有同步任务都执行完毕,系统就会去读取“任务队列”,看看里面有什么事件。那些对应的异步任务结束等待状态,进入执行栈,开始执行。(4)主线程一直重复上面的第三步nextTick。现在我们回到nextTick在vue中的实现很简单,完全是基于语言的执行机制,直接创建一个异步任务,那么nextTick自然会达到在同步任务constp=Promise之后执行的目的。resolve()exportfunctionnextTick(fn?:()=>void):Promise{返回fn?p.then(fn):p}看到这里,可能有同学又要问了,我们前面猜到的DOM更新也是异步任务,那他们的执行顺序怎么保证呢?别担心,nextTick在源代码中有几个兄弟函数。让我们看看queueJob和queuePostFlushCbqueueJob来维护作业队列。它具有重复数据删除逻辑以确保任务的唯一性。每次调用都会执行queueFlushqueuePostFlushCb来维护cb队列。调用去重时,每次调用执行queueFlushconstqueue:(Job|null)[]=[]exportfunctionqueueJob(job:Job){//去重if(!queue.includes(job)){queue.push(job)queueFlush()}}exportfunctionqueuePostFlushCb(cb:Function|Function[]){if(!isArray(cb)){postFlushCbs.push(cb)}else{postFlushCbs.push(...cb)}queueFlush()}queueFlush启动异步任务(nextTick)处理flushJobsfunctionqueueFlush(){//避免重复调用flushJobsif(!isFlushing&&!isFlushPending){isFlushPending=truenextTick(flushJobs)}}flushJobs处理队列,先对队列进行排序,并在队列中执行job,处理完成后再处理postFlushCbs,如果队列没有被清空会递交调用flushJobs清空队列functionflushJobs(seen?:CountMap){isFlushPending=falseisFlushing=trueletjobif(__DEV__){seen=seen||newMap()}//刷新前排序队列。//这确保://1.组件从父级更新到子级。(因为父组件总是//在子组件之前创建,所以它的渲染效果将具有更小的//优先级数)//2.如果组件在父组件更新期间被卸载,则可以跳过其更新。//在刷新开始之前,作业永远不能为空,因为它们只会在//执行另一个刷新作业期间失效。queue.sort((a,b)=>getId(a!)-getId(b!))while((job=queue.shift())!==undefined){if(job===null){继续}if(__DEV__){checkRecursiveUpdates(seen!,job)}callWithErrorHandling(job,null,ErrorCodes.SCHEDULER)}flushPostFlushCbs(seen)isFlushing=false//somepostFlushCb排队作业!//继续冲洗直到排干。if(queue.length||postFlushCbs.length){flushJobs(seen)}}好了,上面都是实现,看来还是没有解决我们的疑惑,我们需要弄清楚queueJob和queuePostFlushCb是怎么调用的//renderer。tsfunctioncreateDevEffectOptions(instance:ComponentInternalInstance):ReactiveEffectOptions{return{scheduler:queueJob,onTrack:instance.rtc?e=>invokeArrayFns(instance.rtc!,e):0,onTrigger:instance.rtg?e=>invokeArrayFns(instance.rtg!,e):void0}}//effect.tsconstrun=(effect:ReactiveEffect)=>{...if(effect.options.scheduler){effect.options.scheduler(effect)}else{effect()}}看到这里是不是觉得豁然开朗了?原来当响应对象发生变化时,执行effect,如果有scheduler这个参数,就会执行scheduler函数,effect会作为参数传入。简单点就是queueJob(effect),嗯,一目了然,这也是data发生变化后页面不会立即更新的原因为什么effect入口要用nextTick一个例子,让大家明白{{num}}for(leti=0;i<100000;i++){num=i}如果没有nextTick更新机制,那么每次num更新值的时候都会触发视图更新。有了nextTick机制,只需要更新一次,那么为什么会有nextTick呢?相信大家心里已经有了答案。总结NextTick是vue中的一种更新策略,也是一种性能优化的手段。基于JS的执行机制,当我们在vue中更改数据时,并不会立即触发视图。如果需要实时获取最新DOM,此时可以手动调用nextTick@JS语音社区