当前位置: 首页 > Web前端 > vue.js

vue源码解读(四)Vue中的异步更新攻略

时间:2023-03-31 21:40:39 vue.js

欢迎star我的github仓库一起学习~目前vue源码学习系列已经更新了6篇~https://github.com/yisha0307/…快速跳转:Vue的双向绑定原理(已完成)浅谈Vue中的VirtualDOM(已完成)Reactdiff与Vuediff的区别Vue中实现异步更新策略(已完成)Vuex的实现理解(已完成)Typescript学习笔记(continuousupdateing)Vue源码中闭包的使用(已完成)异步更新队列参考vue.js官方教程对vue中异步更新队列的解释:可能你没有注意到,Vue更新DOM的时候是异步执行的。只要检测到数据变化,Vue就会打开一个队列,缓冲所有发生在同一个事件循环中的数据变化。如果同一个观察者被多次触发,它只会被推入队列一次。这种缓冲时的重复数据删除对于避免不必要的计算和DOM操作非常重要。然后,在下一个事件循环“tick”中,Vue刷新队列并执行实际(去重)工作。Vue内部尝试使用原生的Promise.then、MutationObserver和setImmediate来实现异步队列。如果执行环境不支持,它将使用setTimeout(fn,0)代替。你为什么这样做?我们来看一个简单的例子:exportdefault{data(){return{test:0};},mounted(){for(leti=0;i<1000;i++){this.test++;}}}根据mounted中的代码,测试总共触发了1000次。如果不是异步更新,watcher触发1000次时,每次都更新view,非常非常耗性能。因此,Vue会使用异步更新操作。如果同一个观察者被多次触发,它只会被推入队列一次。然后在下一个事件循环'tick'中,你只需要更新一次dom。让我们看一下观察者类中的更新方法,了解异步更新代码是如何实现的。update(){/*istanbulignoreelse*/if(this.lazy){this.dirty=true}elseif(this.sync){/*同步,执行run直接渲染视图*///基本不用syncthis.run()}else{/*异步推送到观察者队列,由调度器调用。*/queueWatcher(this)}}exportfunctionqueueWatcher(watcher:Watcher){/*获取watcher的id*/constid=watcher.id/*检查id是否存在,如果存在则跳过,标记如果不存在希腊表has,用于下次检查*/if(has[id]==null){has[id]=trueif(!flushing){/*如果没有flush,就push到队列*/queue.push(watcher)}else{//如果已经刷新,根据它的id拼接watcher//如果已经超过它的id,它将立即运行。让i=queue.length-1while(i>=0&&queue[i].id>watcher.id){i--}queue.splice(Math.max(i,index)+1,0,watcher)}//flush排队if(!waiting){waiting=truenextTick(flushSchedulerQueue)}}}我们可以看到,watcher在更新的时候,调用了一个queueWatcher方法,将watcher异步推送到watcher队列;调用queueWatcher时,首先检查watcher的id,保证同一个watcher不会被多次推送;waiting是一个指标,如果不waiting,flushSchedulerQueue方法会异步执行。flushSchedulerQueueflushSchedulerQueue是下一个tick的回调函数,主要执行watcher中的run方法。看一下源码:/*nextTick回调函数,在下一个tick刷新两个队列,同时运行watchers*/functionflushSchedulerQueue(){flushing=trueletwatcher,id//flush前排序队列。//这确保://1.组件从父级更新到子级。(因为父对象总是//在子对象之前创建)//2.组件的用户观察者在其渲染观察者之前运行(因为//用户观察者在渲染观察者之前创建)//3.如果组件在父组件的观察者运行,//它的观察者可以被跳过。/*对队列进行排序,可以保证:1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是在子组件之前创建的。2.组件的用户观察器在渲染观察器之前运行,因为用户观察器通常比渲染观察器更早创建3.如果组件在父组件观察器运行时被销毁,其观察器执行将被跳过。*///queue:Arrayqueue.sort((a,b)=>a.id-b.id)//不要缓存长度,因为更多的观察者可能会被推送//因为我们运行现有的观察者(index=0;indexMAX_UPDATE_COUNT){warn('你可能有一个无限更新循环'+(watcher.user?`inwatcherwithexpression"${watcher.expression}"`:`inacomponentrenderfunction.`),watcher.vm)break}}}//在重置状态之前保留发布队列的副本/**//*得到队列的选择*/constactivatedQueue=activatedChildren.slice()constupdatedQueue=queue.slice()/*重置调度器的状态*/resetSchedulerState()//调用组件updated和activatedhooks/*使子组件状态适配为active,同时调用activatedhooks*/callActivatedHooks(activatedQueue)/*调用updatedhook*/callUpdateHooks(updatedQueue)//devtoolhook/*istanbulignoreif*/if(devtools&&config.devtools){devtools.emit('flush')}}操作真正的DOM这个时候我们来看看教程中的vue中文代码:{{message}}

varvm=newVue({el:'#example',data:{message:'123'}})vm.message='newmessage'//改变数据vm.$el.textContent==='newmessage'//falseVue.nextTick(function(){vm.$el.textContent==='newmessage'//true})当设置了vm.message='newmessage'时,组件不会立即渲染,而是在dep.notifywatcher后异步推送到scheduler队列,并会被当nextTick为nextTickTry时更新原生的Promise.then和MutationObserver,如果不支持,最差的是使用setTimeout(fn,0)进行异步更新。所以如果你想根据更新后的DOM状态做一些事情,你可以在数据变化后立即使用Vue.nextTick(callback)对真实的DOM进行操作。