Vue中之nextTick函数源码分析详解
1.什么是Vue.nextTick()?
官方文档解释如下:
在下次DOM更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
2.为什么要使用nextTick?
演示Vue {{name}}
如上代码在页面视图上显示bb,但是当我在控制台打印的时候,获取的文本内容还是aa,但是使用nextTick后,获取的文本内容就是最新的内容bb了,因此在这种情况下,我们可以使用nextTick函数了。
上面的代码为什么改变this.name='bb';后,再使用console.log(this.$el.textContent);打印的值还是aa呢?那是因为设置name的值后,DOM还没有更新到,所以获取值还是之前的值,但是我们放到nextTick函数里面的时候,代码会在DOM更新后执行,因此DOM更新后,再去获取元素的值就可以获取到最新值了。
理解DOM更新:在VUE中,当我们修改了data中的某一个值后,并不会立即反应到该el中,vue将对更改的数据放到watcher的一个异步队列中,只有在当前任务空闲时才会执行watcher队列任务,这就有一个延迟时间,因此放到nextTick函数后就可以获取该el的最新值了。如果我们把上面的nextTick改成setTimeout也是可以的。
3.Vue源码详解之nextTick(源码在vue/src/core/util/env.js)
在理解nextTick源码之前,我们先来理解下html5中新增的MutationObserver的API,它的作用是用来监听DOM变动的接口,它能监听一个dom对象发生的子节点删除,属性修改,文本内容修改等等。
nextTick源码如下:
exportconstnextTick=(function(){ constcallbacks=[] letpending=false lettimerFunc functionnextTickHandler(){ pending=false; /* 之所以要slice复制一份出来是因为有的cb执行过程中又会往callbacks中加入内容,比如$nextTick的回调函数里又有$nextTick, 那么这些应该放入到下一个轮次的nextTick去执行,所以拷贝一份,遍历完成即可,防止一直循环下去。 */ constcopies=callbacks.slice(0) callbacks.length=0 for(leti=0;i=9.3.3whentriggeredintoucheventhandlers.It //completelystopsworkingaftertriggeringafewtimes...so,ifnative //Promiseisavailable,wewilluseit: /*istanbulignoreif*/ /* nextTick行为利用了microtask队列,先使用Promise.resolve().then(nextTickHandler)来将异步回调 放入到microtask中,Promise和MutationObserver都可以使用,但是MutationObserver在IOS9.3以上的 WebView中有bug,因此如果满足第一项的话就可以执行,如果没有原生Promise就用MutationObserver。 */ if(typeofPromise!=='undefined'&&isNative(Promise)){ varp=Promise.resolve() varlogError=err=>{console.error(err)} timerFunc=()=>{ p.then(nextTickHandler).catch(logError) //inproblematicUIWebViews,Promise.thendoesn'tcompletelybreak,but //itcangetstuckinaweirdstatewherecallbacksarepushedintothe //microtaskqueuebutthequeueisn'tbeingflushed,untilthebrowser //needstodosomeotherwork,e.g.handleatimer.Thereforewecan //"force"themicrotaskqueuetobeflushedbyaddinganemptytimer. if(isIOS)setTimeout(noop) } }elseif(typeofMutationObserver!=='undefined'&&( isNative(MutationObserver)|| //PhantomJSandiOS7.x MutationObserver.toString()==='[objectMutationObserverConstructor]' )){ //useMutationObserverwherenativePromiseisnotavailable, //e.g.PhantomJSIE11,iOS7,Android4.4 /* 创建一个MutationObserver,observe监听到DOM改动之后执行的回调nextTickHandler */ varcounter=1 varobserver=newMutationObserver(nextTickHandler) vartextNode=document.createTextNode(String(counter)); //使用MutationObserver的接口,监听文本节点的字符内容 observer.observe(textNode,{ characterData:true }); /* 每次执行timerFunc函数都会让文本节点的内容在0/1之间切换,切换之后将新赋值到那个我们MutationObserver监听的文本节点上去。 */ timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) } }else{ //fallbacktosetTimeout /* 如果上面的两种都不支持的话,我们就使用setTimeout来执行 */ timerFunc=()=>{ setTimeout(nextTickHandler,0) } } returnfunctionqueueNextTick(cb?:Function,ctx?:Object){ let_resolve callbacks.push(()=>{ if(cb){ try{ cb.call(ctx) }catch(e){ handleError(e,ctx,'nextTick') } }elseif(_resolve){ _resolve(ctx) } }); /*如果pending为true,表明本轮事件循环中已经执行过timerFunc(nextTickHandler,0)*/ if(!pending){ pending=true timerFunc() } if(!cb&&typeofPromise!=='undefined'){ returnnewPromise((resolve,reject)=>{ _resolve=resolve }) } } })()
整体思路理解:首先nextTick是一个闭包函数,代码立即执行,在理解整体代码之前,我们先来看个类似的demo,如下代码:
演示Vue