发布网友 发布时间: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