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