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

vue.jsnextTick源码分析

时间:2023-03-31 23:33:30 vue.js

nextTickvue2.6.11版本源码分析(nextTick)nextTick源码调用流程总结:init->timerFunc=(Promise/MutationObserver/setImmediate)初始化阶段给执行方法赋值timerFunc的,一般在Windows浏览器环境下运行timerFunc函数的执行方式会是Promise.then方式,使用microtask队列方式。if(typeofPromise!=='undefined'&&isNative(Promise)){varp=Promise.resolve();timerFunc=function(){p.then(flushCallbacks);如果(isIOS){setTimeout(noop);}};isUsingMicroTask=true;}elseif(!isIE&&typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||MutationObserver.toString()==='[objectMutationObserverConstructor]')){varcounter=1;varobserver=newMutationObserver(flushCallbacks);vartextNode=document.createTextNode(字符串(计数器));observer.observe(textNode,{characterData:true});timerFunc=function(){counter=(counter+1)%2;textNode.data=String(计数器);};isUsingMicroTask=true;}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){timerFunc=function(){setImmediate(flushCallbacks);}};}else{timerFunc=function(){setTimeout(flushCallbacks,0);};}$nextTick(fn)->callbacks.push(function(){fn.call(this)})->timerFunc()使用nextTick的源码如下:functionnextTick(cb,ctx){console.log('vuenexttick')var_resolve;callbacks.push(function(){//全局变量回调if(cb){try{cb.call(ctx);//在这里调用回调}catch(e){handleError(e,ctx,'nextTick');}}elseif(_resolve){_resolve(ctx);}});如果(!pending){pending=true;//只执行timerFunc函数timerFunc();}//$flow-disable-lineif(!cb&&typeofPromise!=='undefined'){returnnewPromise(function(resolve){_resolve=resolve;})}}如上所示,在一个宏任务只会调用一次timerFunc(),而timerFunc()会将flushCallbacks函数放入JavaScript的微任务队列中依次调用......varp=Promise.resolve();timerFunc=function(){p.then(flushCallbacks);如果(isIOS){setTimeout(noop);}};......functionflushCallbacks(){pending=false;var副本=callbacks.slice(0);回调.length=0;对于(vari=0;iindex&&queue[i].id>watcher.id){i--;}queue.splice(i+1,0,观察者);}//排队刷新if(!waiting){waiting=true;if(process.env.NODE_ENV!=='production'&&!config.async){flushSchedulerQueue();返回}nextTick(flushSchedulerQueue);//nextTickflushSchedulerQueue}}}关键:flushSchedulerQueue函数是干什么的:1.遍历queue变量获取watcher2.watcher.before()调用,此时是组件生命周期中beforeUpdate回调通知的时间。3、调用watcher.run()时,如果watcher对应的组件配置了watch,此时会执行回调,更新数据和DOM。4.当调用resetSchedulerState()时,waiting=false。这个时候,数据已经更新了。下次触发reactiveSetter时,将再次调用nextTick。5.CallUpdatedHooks,callActivatedHooks调用,分别对应生命周期中的activated和updated。functionflushSchedulerQueue(){//调试器currentFlushTimestamp=getNow();冲洗=真;变量观察者,id;queue.sort(function(a,b){returna.id-b.id;});for(index=0;index{console.log('promiseimmediate111')resolve('ok')}).then(()=>{console.log('promisethen111')})this.$nextTick(()=>{console.log('nexcTick111')})newPromise((resolve,reject)=>{console.log('promiseimmediate222')resolve('ok')}).then(()=>{console.log('promisethen222')})this.$nextTick(()=>{console.log('nexcTick222')})this.visible=false//数据操作this.$nextTick(()=>{console.log('nexcTick333')})以上代码,根据我的初步认知,输出顺序应该是:promiseimmediate111promiseimmediate222nexcTick111promisethen222nexcTick222nexcTick333但事实并非如此,操作数据触发了reactiveSetter,它实际上添加了微任务队列的顺序是:1.promisethen111microtask12.nexcTick111->callbacks3.flushCallbacksfunctionmicrotask24.promisethen222microtask35.nexcTick222->callbacks6.settercall,flushSchedulerQueue->callbacks7,nexcTick333->Vue源代码中的回调next将控制台输出添加到Tick函数以验证猜想:functionnextTick(cb,ctx){var_resolve;console.log(`${cb.name?cb.name:'arrowfunction'}添加回调`)callbacks.push(...);如果(!pending){pending=true;定时器功能();}}结果:总结一下触发setter时的整体流程:1、ReactiveSetter首先改变data对象中的值,但是DOM还没有更新,可以说先保存2、dep.notify。每个watcher3,这里通知这个组件[dependency]的Watcher.update。这里调用queueWatcher让wacher进入队列准备更新。另:如果强制同步更新DOM,这里会执行this.run()来执行相应的DOM更新操作。4.队列观察者。这里让watcher进入待执行队列,如果是本次update操作的第一个setter,则调用nextTick函数,让flushSchedulerQueue函数加入microtask队列。5.刷新调度队列。这里函数开始执行,代表宏任务执行完毕,启动微任务队列。这里,会经历beforeUpdate->updateDOM->updated的过程。nextTick触发时的整体流程:0、timerFunc赋值。根据操作系统的不同,Promise方法通常用于执行异步任务。1.下一步。将要执行的回调添加到回调队列。如果该函数在更新周期中第一次执行,则调用timerFunc将flushCallbacks函数添加到微任务队列中。2.刷新回调。这里依次遍历callbacks队列中待执行的任务,依次执行。这时候可能有用户自己调用的nextTick回调,也可能中途执行了setter操作,插入了flushSchedulerQueue回调。在flushSchedulerQueue任务之前和之后执行代码是完全不同的,这就是为什么在写代码的过程中可能会出现意想不到的情况。总结:对于Vue中微任务的处理,虽然只插入了一个微任务,但是存储在数组中的待处理任务,即使是后面执行的setter或者nextTick,也可以按照最先执行的nextTick或者setter调用实现的优先顺序进行排序。有一种插队的感觉。源码flushSchedulerQueue中watcher.before函数的一部分没有提到,对应beforeUpdate生命周期newWatcher(vm,updateComponent,noop,{before:functionbefore(){if(vm._isMounted&&!vm._isDestroyed){callHook(vm,'beforeUpdate');}}},true/*isRenderWatcher*/);watcher.run函数中的flushSchedulerQueue,此时更新数据if(value!==this.value||isObject(value)||this.deep){//设置新值varoldValue=this.value;this.value=值;if(this.user){varinfo="回调观察者\""+(this.expression)+"\"";invokeWithErrorHandling(this.cb,this.vm,[value,oldValue],this.vm,info);}else{this.cb.call(this.vm,value,oldValue);//这里是手表回调}}}};flushSchedulerQueue中的callUpdatedHooks函数,生命周期updatedfunctioncallUpdatedHooks(queue){vari=q队列长度;while(i--){varwatcher=queue[i];varvm=watcher.vm;if(vm._watcher===watcher&&vm._isMounted&&!vm._isDestroyed){callHook(vm,'更新');}}}