问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

浅曦Vue源码-42-patch阶段-$nextTick&异步队列更新

发布网友 发布时间:2024-09-27 09:43

我来回答

1个回答

热心网友 时间:2024-10-06 07:57

一、前情回顾&背景

上一篇小作文作为patch阶段的第一篇主要做了以下工作:

重新修改test.html加入了可以修改响应式数据的button#btn元素,以及绑定点击事件修改data.forProp.a;

重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了Watcher、Dep以及响应式数据间的依赖和被依赖关系以及三者协作过程;

通过修改this.forProp.a进入到了dep.notify(),接着看到了作为计算属性的lazywatcher和普通watcher在watcher.update()方法中的不同处理方式;

因为作为就算属性的lazywatcher要等到用到的时候才会求值,所以放到后面再说,本篇小作文的接着讲把要更新的watcher作为参数传递给queueWatcher方法后的事情;

二、queueWatcher

方法位置:src/core/observer/scheduler.js->functionqueueWatcher

方法参数:watcher,待更新的Watcher实例

方法作用:将watcher推入watcher队列,id相同的watcher将会被忽略,但是当队列正在被刷新时例外,具体如下:

获取watcher.id,watcher.id是一个自增的数字,数字越小标识这个watcher的创建的顺序越靠前

判重,如果不存在该id再处理,并且缓存该watcher.id;

2.1如果队列未处于正在刷新状态,即flushing不为true,则将该watcher推入队列

2.2否则,从队列末尾向前遍历找到比当前watcher.id小的那个,把当前watcher插入id较小的那个后面;

判断waiting标识符,第一次执行queueWatcher时waiting是false,但是执行过一次queueWatcher后就被置为true了。这么做确保本次事件循环中只会在下一个循环中添加一个flushSchedulerQueue任务;这也是常说的Vue会合并更新,然后在下个事件循环中全量更新。在当前循环中收集要更新的watcher放入队列,而不是立刻执行这个watcher。

经过第一个watcher.update调用queueWatcher的三步骤后,全局变量waiting变为false,如果dep.notify中还有watcher需要update,那么仍然会调用queueWatcher,那这个时候咋办呢?

因为dep.notify是for循环这种同步代码,连续调用subs[i].update(),对于queueWatcher来说,浏览器的下一个事件循环中已经有刷新队列的任务了——flushSchedulerQueue;只管向队列中添加watcher就好了,当下一个事件循环开始的时候就会消耗这个队列;

exportfunctionqueueWatcher(watcher:Watcher){constid=watcher.id//如果watcher已经存在,就不处理,保证不会重复进入队列if(has[id]==null){//缓存watcher.id,用于判断watcher是否已经进入队列has[id]=trueif(!flushing){//flushing标识当前队列是否正在被刷新//当前没处在刷新队列状态,watcher直接进入队列queuequeue.push(watcher)}else{//如果已经在刷新队列正处于被刷新的状态,//从queue末尾开始遍历,根据当前watcher.id,找到id比它小的watcher位置,//然后将自己插入到这小id的watcher的下一个位置//即将当前watcher放入到已经排序的队列queue中//至于为啥是队列,后面会解释的leti=queue.length-1while(i>index&&queue[i].id>watcher.id){i--}queue.splice(i+1,0,watcher)}//queuetheflushif(!waiting){waiting=trueif(process.env.NODE_ENV!=='production'&&!config.async){//直接同步刷新队列,不是重点,忽略flushSchedulerQueue()return}//nextTick就是Vue.nextTick或者this.$nextTick//其主要作用有两点://1.就是把刷新queue队列的flushSchdulerQueue放入callbacks列表//2.通过pending控制浏览器中只有一个刷新callbacks的flushCallbacks任务nextTick(flushSchedulerQueue)}}}2.1flushSchedulerQueue

方法位置:src/core/observer/scheduler.js

方法参数:无

方法作用:

维护flushing为true;

给queue里面的watcher进行排序,排序的意义在于:

2.1确保组件更新顺序从父级到子级,因为父组件总是在子组件之前被创建

2.2?一个组件的用户watcher(你自己写在代码里面的watch叫做用户watcher)在渲染watcher之前被执行,因为用户watcher先于渲染watcher创建

2.3如果一个组件在其父组件的watcher执行期间被销毁,则它的watcher会被跳过

遍历queue,逐个调用queue中的每个watcher.before(如有),然后调用watcher.run重新求值;

调用resetSchedulerState重置wating和flushing标识符;

触发activated和updated组件的生命周期钩子

functionflushSchedulerQueue(){currentFlushTimestamp=getNow()flushing=trueletwatcher,id

//刷新队列之前先给队列排序(升序),可以保证://1.组件更新顺序从父级到子级,因为父组件总是在子组件之前被创建//2.一个组件的用户watcher(你自己写在代码里面的watcher叫做用户watcher)//??在渲染watcher之前被执行,因为用户watcher先于渲染watcher创建//3.如果一个组件在其父组件的watcher执行期间被销毁,则它的watcher会被跳过//??排序以后再刷新队列期间新进来的watcher也会按顺序放入队列的合适位置

queue.sort((a,b)=>a.id-b.id)

//不要缓存queue.length//简介利用了数组长度是个动态更新的值,这有啥好处呢?//因为在执行当前watcher时,//队列中可能会被push进来更多watcherfor(index=0;index<queue.length;index++){watcher=queue[index]//执行before钩子,在使用vm.$watch?//或者watch选项时可以选配options.before传递if(watcher.before){watcher.before()}//将缓存的watcher清除id=watcher.idhas[id]=null//执行watcher.run(),最终触发更新函数,//比如渲染watcher的updateComponentwatcher.run()}

//在重置状态(flushing/wating)前复制保存激活的子列表constactivatedQueue=activatedChildren.slice()constupdatedQueue=queue.slice()

resetSchedulerState()//这里会把waiting重置为false

//调用组件的updated和activated钩子callActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)}

####2.1.1wathcer.before上面的`flushSecheduleQueue`中调用了`watcher.before`,下面就是一个创建`渲染watcher`时传递的`before`选项;```jsexportfunctionmountComponent():Component{letupdateComponentif(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{vm._update(vm._render(),hydrating)}}//这个玩意儿就是渲染watchernewWatcher(vm,updateComponent,noop,{before(){//这个before方法将会称为watcher.before//在响应式更新后watcher被重新求值前调用if(vm._isMounted&&!vm._isDestroyed){callHook(vm,'beforeUpdate')}}},true)}2.1.2Watcher.prototype.run

这个方法就是被上面flushSchedulerQueue调用的watcher.run,其主要作用就是调用创建watcher时传递的回调函数:

对于渲染watcher就是updateComponent方法;

对于用户watcher就是监听到值变化时要执行的回调函数,所谓用户watcher就是我们在Vue组件中传递的watch选项例如,{watch:{someVal(newVal,oldVal){....}}};

exportdefaultclassWatcher{constructor(){this.before=options.beforethis.getter=expOrFnthis.value=this.lazy?undefined:this.get()}get(){pushTarget(this)letvalueconstvm=this.vmtry{//执行回调函数updateComponent,进入patch阶段value=this.getter.call(vm,vm)}catch(e){}finally{}returnvalue}update(){queueWatcher(this)}run(){if(this.active){//调用this.get方法对watcher重新求值constvalue=this.get()if(value!==this.value||//deepwathcer和Object/Array的watcher即便是同一个值也要触发重新计算//因为有可能其中的keyvalue已经发生了变化isObject(value)||this.deep){//setnewvalue//缓存旧值为之前的valueconstoldValue=this.value//更新value为最新求得的valuethis.value=valueif(this.user){//如果是用户watcher,则执行用户传递的第三个参数——回调函数,//参数为val和oldValconstinfo=`callbackforwatcher"${this.expression}"`invokeWithErrorHandling(this.cb,this.vm,[value,oldValue],this.vm,info)}else{//更新一个渲染watcher时,//也就是说这个run方法是由渲染watcher.run调用,//其cb是调用了updateComponent方法this.cb.call(this.vm,value,oldValue)}}}}}2.2nextTick

方法位置:src/core/util/next-tick.js->functionnextTick

方法参数:

cb,下一个tick需要调用的回调函数,经过包装放到callbacks列表中;

ctx,cb触发时指定的上下文对象

方法作用:

包装cb函数,放入callbacks队列中,这队列将会由flushCallbacks消耗,在我们目前patch阶段中的cb是flushQueueWatcher方法,这个方法被放到callbacks队列中,当触发时执行watcher.run方法对watcher重新求值;

维护pending,前面说了nextTick需要保证浏览器在下个事件环的任务队列中只有flushCallback;保证方法也很简单,第一次执行置标识符pending为true,后面再执行的时候判断pending为true就不添加了。当flushCallbacks执行后再将pending置为false就可以了。

exportfunctionnextTick(cb?:Function,ctx?:Object){let_resolvecallbacks.push(()=>{if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){_resolve(ctx)}})if(!pending){//维护pending为true,//确保这个下个事件循环中只有一个flushCallbackspending=true//timerFunc负责把flushCallbacks放入到下个事件循环中timerFunc()}//$flow-disable-lineif(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}

为啥叫nextTick呢?tick是个事件循环的概念,表示的浏览器从收到通知后从任务队列中取出一个任务,然后执行它这个全套过程叫做一个tick。所以nexttick顾名思义,放到下一次tick执行;

有很多人估计看到过一个经典面试题:说说$nextTick的原理。估计很多人都知道$nextTick中关于如何把回调函数放到下一个tick中的降级过程,优先使用Promise.then,如果没有Promise则使用MutationObserver,如果前两个都没有尝试setImmediate,如果前面都没有就用setTimeout;

那么这些逻辑都是在哪里处理的呢?没错timerFunc方法~

2.1.1timerFunc

方法位置:src/core/util/next-tick.js->lettimerFunc

方法参数:无

方法作用:通过js的异步任务,将flushCallbacks放到下一个事件循环。在处理这个问题的时候是存在优先级的,优先使用微任务,实在不行再使用宏任务,优先级按顺序如下:

原生的Promise.then优先级最高,将flushCallbacks放到下一个事件循环开始前的微任务队列;

如果原生Promise不被支持,则降级到MuatationObserver;

前面两个微任务都不被支持,看下setImmedaite这个宏任务是否支持,若支持则使用;

最后用setTimeout作为兜底选项使用;

lettimerFunc//nextTick行为充分利用微任务对队列,//通过原生Promise或者MutationObserver实现//MutationObserver虽然被广泛支持,//但是在ios>=9.3.3的UIWebView仍然存在严重问题if(typeofPromise!=='undefined'&&isNative(Promise)){constp=Promise.resolve()timerFunc=()=>{//在微任务队列中放入flushCallbacksp.then(flushCallbacks)//在有问题的UIWebViews中,Promise.then不会完全退出,而是会陷入怪异状态,//在这种状态下,回调被推入微任务队列,但是队列没有被刷新,//直至浏览器需要执行其他工作时才会刷新,比如处理定时器,//因此我们可以通过添加空的定时器来强制刷新微任务队列if(isIOS)setTimeout(noop)}isUsingMicroTask=true}elseif(!isIE&&typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||//PhantomJSandiOS7.xMutationObserver.toString()==='[objectMutationObserverConstructor]')){//在原生的Promise不可用的时候,MutationsObserver次之//比如PhantomJS,ios7,android4.4//IE11仍不可用letcounter=1constobserver=newMutationObserver(flushCallbacks)consttextNode=document.createTextNode(String(counter))observer.observe(textNode,{characterData:true})timerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}isUsingMicroTask=true}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){//再次之是setImmediate//虽然是一个宏任务了,但仍比setTimeout要好timerFunc=()=>{setImmediate(flushCallbacks)}}else{//最后用setTimeout兜底timerFunc=()=>{setTimeout(flushCallbacks,0)}}2.2.2flushCallbacks

方法位置:src/core/util/next-tick.js

方法参数:无

方法作用:消耗callbacks队列,赋值callback中的函数,然后清空callbacks队列;

functionflushCallbacks(){pending=falseconstcopies=callbacks.slice(0)callbacks.length=0//遍历copies数组,//数组中存储的是flushSchedulerQueue包装函数for(leti=0;i<copies.length;i++){copies[i]()}}

callbacks存放就是上面2.1的flushSchedulerQueue函数,这么说其实并不准确,它存放的是一个被包装过的函数,这个包装过程发生在nextTick中:

exportfunctionnextTick(cb?:Function,ctx?:Object){//...//cb就是flushSchedulerQueue方法callbacks.push(()=>{//就是这个包装函数,主要是处理cb执行时的错误if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){_resolve(ctx)}})//....}三、总结

本篇小作文讲述了Vue如何组织队列更新的,主要依托于下面几个方法:

Watcher.prototype.update,当响应式数据发生变化,其对应的dep.notify执行,watcher.update会调用queueWatcher;

queueWatcher负责把watcher实例加入到待求值的watcher队列queue中,添加到队列需要根据当前队列是否处于刷新状态做不同的处理;

queueWatcher还会调用nextTick方法,传入消耗queue队列的flushSchedulerQueue方法;

nextTick会把flushSchedulerQueue包装然后放到callbacks队列,nextTick另一个重要任务就是把消耗callbacks队列的flushCallback放入到下一个事件循环(或者下一个事件循环的开头,即微任务);

原文:https://juejin.cn/post/7101656752818487304

热心网友 时间:2024-10-06 07:54

一、前情回顾&背景

上一篇小作文作为patch阶段的第一篇主要做了以下工作:

重新修改test.html加入了可以修改响应式数据的button#btn元素,以及绑定点击事件修改data.forProp.a;

重新梳理了完整的响应式流程,包含依赖收集、修改数据、派发更新的过程;并且明确了Watcher、Dep以及响应式数据间的依赖和被依赖关系以及三者协作过程;

通过修改this.forProp.a进入到了dep.notify(),接着看到了作为计算属性的lazywatcher和普通watcher在watcher.update()方法中的不同处理方式;

因为作为就算属性的lazywatcher要等到用到的时候才会求值,所以放到后面再说,本篇小作文的接着讲把要更新的watcher作为参数传递给queueWatcher方法后的事情;

二、queueWatcher

方法位置:src/core/observer/scheduler.js->functionqueueWatcher

方法参数:watcher,待更新的Watcher实例

方法作用:将watcher推入watcher队列,id相同的watcher将会被忽略,但是当队列正在被刷新时例外,具体如下:

获取watcher.id,watcher.id是一个自增的数字,数字越小标识这个watcher的创建的顺序越靠前

判重,如果不存在该id再处理,并且缓存该watcher.id;

2.1如果队列未处于正在刷新状态,即flushing不为true,则将该watcher推入队列

2.2否则,从队列末尾向前遍历找到比当前watcher.id小的那个,把当前watcher插入id较小的那个后面;

判断waiting标识符,第一次执行queueWatcher时waiting是false,但是执行过一次queueWatcher后就被置为true了。这么做确保本次事件循环中只会在下一个循环中添加一个flushSchedulerQueue任务;这也是常说的Vue会合并更新,然后在下个事件循环中全量更新。在当前循环中收集要更新的watcher放入队列,而不是立刻执行这个watcher。

经过第一个watcher.update调用queueWatcher的三步骤后,全局变量waiting变为false,如果dep.notify中还有watcher需要update,那么仍然会调用queueWatcher,那这个时候咋办呢?

因为dep.notify是for循环这种同步代码,连续调用subs[i].update(),对于queueWatcher来说,浏览器的下一个事件循环中已经有刷新队列的任务了——flushSchedulerQueue;只管向队列中添加watcher就好了,当下一个事件循环开始的时候就会消耗这个队列;

exportfunctionqueueWatcher(watcher:Watcher){constid=watcher.id//如果watcher已经存在,就不处理,保证不会重复进入队列if(has[id]==null){//缓存watcher.id,用于判断watcher是否已经进入队列has[id]=trueif(!flushing){//flushing标识当前队列是否正在被刷新//当前没处在刷新队列状态,watcher直接进入队列queuequeue.push(watcher)}else{//如果已经在刷新队列正处于被刷新的状态,//从queue末尾开始遍历,根据当前watcher.id,找到id比它小的watcher位置,//然后将自己插入到这小id的watcher的下一个位置//即将当前watcher放入到已经排序的队列queue中//至于为啥是队列,后面会解释的leti=queue.length-1while(i>index&&queue[i].id>watcher.id){i--}queue.splice(i+1,0,watcher)}//queuetheflushif(!waiting){waiting=trueif(process.env.NODE_ENV!=='production'&&!config.async){//直接同步刷新队列,不是重点,忽略flushSchedulerQueue()return}//nextTick就是Vue.nextTick或者this.$nextTick//其主要作用有两点://1.就是把刷新queue队列的flushSchdulerQueue放入callbacks列表//2.通过pending控制浏览器中只有一个刷新callbacks的flushCallbacks任务nextTick(flushSchedulerQueue)}}}2.1flushSchedulerQueue

方法位置:src/core/observer/scheduler.js

方法参数:无

方法作用:

维护flushing为true;

给queue里面的watcher进行排序,排序的意义在于:

2.1确保组件更新顺序从父级到子级,因为父组件总是在子组件之前被创建

2.2?一个组件的用户watcher(你自己写在代码里面的watch叫做用户watcher)在渲染watcher之前被执行,因为用户watcher先于渲染watcher创建

2.3如果一个组件在其父组件的watcher执行期间被销毁,则它的watcher会被跳过

遍历queue,逐个调用queue中的每个watcher.before(如有),然后调用watcher.run重新求值;

调用resetSchedulerState重置wating和flushing标识符;

触发activated和updated组件的生命周期钩子

functionflushSchedulerQueue(){currentFlushTimestamp=getNow()flushing=trueletwatcher,id

//刷新队列之前先给队列排序(升序),可以保证://1.组件更新顺序从父级到子级,因为父组件总是在子组件之前被创建//2.一个组件的用户watcher(你自己写在代码里面的watcher叫做用户watcher)//??在渲染watcher之前被执行,因为用户watcher先于渲染watcher创建//3.如果一个组件在其父组件的watcher执行期间被销毁,则它的watcher会被跳过//??排序以后再刷新队列期间新进来的watcher也会按顺序放入队列的合适位置

queue.sort((a,b)=>a.id-b.id)

//不要缓存queue.length//简介利用了数组长度是个动态更新的值,这有啥好处呢?//因为在执行当前watcher时,//队列中可能会被push进来更多watcherfor(index=0;index<queue.length;index++){watcher=queue[index]//执行before钩子,在使用vm.$watch?//或者watch选项时可以选配options.before传递if(watcher.before){watcher.before()}//将缓存的watcher清除id=watcher.idhas[id]=null//执行watcher.run(),最终触发更新函数,//比如渲染watcher的updateComponentwatcher.run()}

//在重置状态(flushing/wating)前复制保存激活的子列表constactivatedQueue=activatedChildren.slice()constupdatedQueue=queue.slice()

resetSchedulerState()//这里会把waiting重置为false

//调用组件的updated和activated钩子callActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)}

####2.1.1wathcer.before上面的`flushSecheduleQueue`中调用了`watcher.before`,下面就是一个创建`渲染watcher`时传递的`before`选项;```jsexportfunctionmountComponent():Component{letupdateComponentif(process.env.NODE_ENV!=='production'&&config.performance&&mark){updateComponent=()=>{vm._update(vm._render(),hydrating)}}//这个玩意儿就是渲染watchernewWatcher(vm,updateComponent,noop,{before(){//这个before方法将会称为watcher.before//在响应式更新后watcher被重新求值前调用if(vm._isMounted&&!vm._isDestroyed){callHook(vm,'beforeUpdate')}}},true)}2.1.2Watcher.prototype.run

这个方法就是被上面flushSchedulerQueue调用的watcher.run,其主要作用就是调用创建watcher时传递的回调函数:

对于渲染watcher就是updateComponent方法;

对于用户watcher就是监听到值变化时要执行的回调函数,所谓用户watcher就是我们在Vue组件中传递的watch选项例如,{watch:{someVal(newVal,oldVal){....}}};

exportdefaultclassWatcher{constructor(){this.before=options.beforethis.getter=expOrFnthis.value=this.lazy?undefined:this.get()}get(){pushTarget(this)letvalueconstvm=this.vmtry{//执行回调函数updateComponent,进入patch阶段value=this.getter.call(vm,vm)}catch(e){}finally{}returnvalue}update(){queueWatcher(this)}run(){if(this.active){//调用this.get方法对watcher重新求值constvalue=this.get()if(value!==this.value||//deepwathcer和Object/Array的watcher即便是同一个值也要触发重新计算//因为有可能其中的keyvalue已经发生了变化isObject(value)||this.deep){//setnewvalue//缓存旧值为之前的valueconstoldValue=this.value//更新value为最新求得的valuethis.value=valueif(this.user){//如果是用户watcher,则执行用户传递的第三个参数——回调函数,//参数为val和oldValconstinfo=`callbackforwatcher"${this.expression}"`invokeWithErrorHandling(this.cb,this.vm,[value,oldValue],this.vm,info)}else{//更新一个渲染watcher时,//也就是说这个run方法是由渲染watcher.run调用,//其cb是调用了updateComponent方法this.cb.call(this.vm,value,oldValue)}}}}}2.2nextTick

方法位置:src/core/util/next-tick.js->functionnextTick

方法参数:

cb,下一个tick需要调用的回调函数,经过包装放到callbacks列表中;

ctx,cb触发时指定的上下文对象

方法作用:

包装cb函数,放入callbacks队列中,这队列将会由flushCallbacks消耗,在我们目前patch阶段中的cb是flushQueueWatcher方法,这个方法被放到callbacks队列中,当触发时执行watcher.run方法对watcher重新求值;

维护pending,前面说了nextTick需要保证浏览器在下个事件环的任务队列中只有flushCallback;保证方法也很简单,第一次执行置标识符pending为true,后面再执行的时候判断pending为true就不添加了。当flushCallbacks执行后再将pending置为false就可以了。

exportfunctionnextTick(cb?:Function,ctx?:Object){let_resolvecallbacks.push(()=>{if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){_resolve(ctx)}})if(!pending){//维护pending为true,//确保这个下个事件循环中只有一个flushCallbackspending=true//timerFunc负责把flushCallbacks放入到下个事件循环中timerFunc()}//$flow-disable-lineif(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}

为啥叫nextTick呢?tick是个事件循环的概念,表示的浏览器从收到通知后从任务队列中取出一个任务,然后执行它这个全套过程叫做一个tick。所以nexttick顾名思义,放到下一次tick执行;

有很多人估计看到过一个经典面试题:说说$nextTick的原理。估计很多人都知道$nextTick中关于如何把回调函数放到下一个tick中的降级过程,优先使用Promise.then,如果没有Promise则使用MutationObserver,如果前两个都没有尝试setImmediate,如果前面都没有就用setTimeout;

那么这些逻辑都是在哪里处理的呢?没错timerFunc方法~

2.1.1timerFunc

方法位置:src/core/util/next-tick.js->lettimerFunc

方法参数:无

方法作用:通过js的异步任务,将flushCallbacks放到下一个事件循环。在处理这个问题的时候是存在优先级的,优先使用微任务,实在不行再使用宏任务,优先级按顺序如下:

原生的Promise.then优先级最高,将flushCallbacks放到下一个事件循环开始前的微任务队列;

如果原生Promise不被支持,则降级到MuatationObserver;

前面两个微任务都不被支持,看下setImmedaite这个宏任务是否支持,若支持则使用;

最后用setTimeout作为兜底选项使用;

lettimerFunc//nextTick行为充分利用微任务对队列,//通过原生Promise或者MutationObserver实现//MutationObserver虽然被广泛支持,//但是在ios>=9.3.3的UIWebView仍然存在严重问题if(typeofPromise!=='undefined'&&isNative(Promise)){constp=Promise.resolve()timerFunc=()=>{//在微任务队列中放入flushCallbacksp.then(flushCallbacks)//在有问题的UIWebViews中,Promise.then不会完全退出,而是会陷入怪异状态,//在这种状态下,回调被推入微任务队列,但是队列没有被刷新,//直至浏览器需要执行其他工作时才会刷新,比如处理定时器,//因此我们可以通过添加空的定时器来强制刷新微任务队列if(isIOS)setTimeout(noop)}isUsingMicroTask=true}elseif(!isIE&&typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||//PhantomJSandiOS7.xMutationObserver.toString()==='[objectMutationObserverConstructor]')){//在原生的Promise不可用的时候,MutationsObserver次之//比如PhantomJS,ios7,android4.4//IE11仍不可用letcounter=1constobserver=newMutationObserver(flushCallbacks)consttextNode=document.createTextNode(String(counter))observer.observe(textNode,{characterData:true})timerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}isUsingMicroTask=true}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){//再次之是setImmediate//虽然是一个宏任务了,但仍比setTimeout要好timerFunc=()=>{setImmediate(flushCallbacks)}}else{//最后用setTimeout兜底timerFunc=()=>{setTimeout(flushCallbacks,0)}}2.2.2flushCallbacks

方法位置:src/core/util/next-tick.js

方法参数:无

方法作用:消耗callbacks队列,赋值callback中的函数,然后清空callbacks队列;

functionflushCallbacks(){pending=falseconstcopies=callbacks.slice(0)callbacks.length=0//遍历copies数组,//数组中存储的是flushSchedulerQueue包装函数for(leti=0;i<copies.length;i++){copies[i]()}}

callbacks存放就是上面2.1的flushSchedulerQueue函数,这么说其实并不准确,它存放的是一个被包装过的函数,这个包装过程发生在nextTick中:

exportfunctionnextTick(cb?:Function,ctx?:Object){//...//cb就是flushSchedulerQueue方法callbacks.push(()=>{//就是这个包装函数,主要是处理cb执行时的错误if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){_resolve(ctx)}})//....}三、总结

本篇小作文讲述了Vue如何组织队列更新的,主要依托于下面几个方法:

Watcher.prototype.update,当响应式数据发生变化,其对应的dep.notify执行,watcher.update会调用queueWatcher;

queueWatcher负责把watcher实例加入到待求值的watcher队列queue中,添加到队列需要根据当前队列是否处于刷新状态做不同的处理;

queueWatcher还会调用nextTick方法,传入消耗queue队列的flushSchedulerQueue方法;

nextTick会把flushSchedulerQueue包装然后放到callbacks队列,nextTick另一个重要任务就是把消耗callbacks队列的flushCallback放入到下一个事件循环(或者下一个事件循环的开头,即微任务);

原文:https://juejin.cn/post/7101656752818487304
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
武汉民政职业学院地址在哪里 全国有哪些民政学院 武汉民政学院 北京哪些区较好 北京哪个区环境好 北京海淀区属于什么档次 北京市哪个区最好 北京哪个区房子最好 递延是什么意思通俗 婚后是不是一定要自己买房子才可以结婚呢? Vue2ElementSchemaForm配置式生成表单的实现 三生石好象有两个版本啊 vue中时间戳转换日期格式 如何在 Vue 的计算属性中传递参数 梦见等公交车很久没来是什么预兆 我是否可以问问通过《演员的诞生》,让你悟出啥道理了吗? 综艺节目《演员的诞生》,因为演员揭露节目组造假、博收视率,闹的沸沸... 双喜硬经典多少钱一条 你感觉《演员的诞生》这个综艺节目怎么样呢? 你是如何看待《演员的诞生》这个综艺? 司马迁在史记中为什么对张骞的评价是"凿空" 163企业邮箱 美发DIY懒人必备三分钟速成编发教程 钓鲤鱼用白酒泡玉米行吗?野钓鲤鱼玉米泡制方法 梦见逮鸽子是什么意思? 钓草鱼用白酒泡玉米好用么? ...暗藏石中寻猎物, 金屋光辉二一景, 三三花红连理枝 ,打一 生肖... 寻找电影!一个孩子只要在雷雨天就会长大的,最后在雷雨中的闪电死去... 身上不知是被咬的还是什么起红疙瘩很大疙瘩周围很红,而且三三两集中在胳... 人微言轻的下一句 双喜牌香烟价格表1906 饲养场有白兔35只比黑兔的指数15%多十五只饲养场有灰兔多少只 35加百分之十五怎么算 ...15个卖出的和剩下的各占布娃娃走。总数的百分之几 什么叫拉仇 怎么更改win10账户名称呢? 跑完步想吐是什么引起的 跑完步恶心是怎么回事啊? 情绪指标概述 麦博M200BT怎么样麦博M200BT好吗 跑完步嘴里很苦什么情况? 2个月没运动了。以前跑步我都是算好的,然而... 麦博d22蓝牙户外骑行音箱怎么样 怎么跑完步后有想呕吐的显象?怎么回事 洪湖水浪打浪歌词歌谱 远交近攻成语相关 永远的近义词和反义词是什么_永远是什么意思? 花卉枝条生根方法 黯淡 永远 诚实 华贵 居然 纯熟的反义词 近义词 网上下的红蓝光3D电影,买一个3D眼镜,在电脑上可以直接看吗?_百度知 ... 如何泡制钓鱼玉米