浅谈Vue.nextTick 的实现方法
这是一篇继eventloop和MicroTask后的vue.nextTickAPI实现的源码解析。
预热,写一个sleep函数
functionsleep(ms){ returnnewPromise(resolve=>setTimeout(resolve,ms) } asyncfunctiononeTick(ms){ console.log('start') awaitsleep(ms) console.log('end') } oneTick(3000)
解释下sleep函数
async函数进行awaitPromiseFn()时函数执行是暂停的,我们也知道现在这个PromiseFn是在microTask内执行。当microTask没执行完毕时,后面的macroTask是不会执行的,我们也就通过microTask在eventloop的特性实现了一个sleep函数,阻止了console.log的执行
流程
1执行console.log('start')
2执行await执行暂停,等待await函数后的PromiseFn在microTask执行完毕
3在sleep函数内,延迟ms返回
4返回resolve后执行console.log('end')
nextTickAPI
vue中nextTick的使用方法
vue.nextTick(()=>{ //todo... })
了解用法后看一下源码
constnextTick=(function(){ constcallbacks=[] letpending=false lettimerFunc//定时函数 functionnextTickHandler(){ pending=false constcopies=callbacks.slice(0)//复制 callbacks.length=0//清空 for(leti=0;i{console.error(err)} timerFunc=()=>{ p.then(nextTickHandler).catch(logError)//重点 } }elseif('!isIEMutationObserver'){ varcounter=1 varobserver=newMutationObserver(nextTickHandler)//重点 vartextNode=document.createTextNode(string(conter)) observer.observe(textNode,{ characterData:true }) timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) } }else{ timerFunc=()=>{ setTimeout(nextTickHandler,0)//重点 } } returnfunctionqueueNextTick(cb,ctx){//api的使用方式 let_resolve callbacks.push(()=>{ if(cb){ try{ cb.call(ctx) }catch(e){ err } }elseif(_resolve){ _resolve(ctx) } }) if(!pending){ pending=true timerFunc() } if(!cb&&typeofPromise!=='undefined'){ returnnewPromise((resolve,reject)=>{ _resolve=resolve }) } } })()//自执行函数
大致看一下源码可以了解到nextTickapi是一个自执行函数
既然是自执行函数,直接看它的return类型,returnfunctionqueueNextTick(cb,ctx){...}
returnfunctionqueueNextTick(cb,ctx){//api的使用方式 let_resolve callbacks.push(()=>{ if(cb){ try{ cb.call(ctx) }catch(e){ err } }elseif(_resolve){ _resolve(ctx) } }) if(!pending){ pending=true timerFunc() } if(!cb&&typeofPromise!=='undefined'){ returnnewPromise((resolve,reject)=>{ _resolve=resolve }) } }
只关注主流程queueNextTick函数把我们传入的()=>{//todo...}推入了callbacks内
if(typeofPromise!=='undefined'&&isNative(Promise)){ varp=Promise.resolve() varlogError=err=>{console.error(err)} timerFunc=()=>{ p.then(nextTickHandler).catch(logError)//重点 } }elseif('!isIEMutationObserver'){ varcounter=1 varobserver=newMutationObserver(nextTickHandler)//重点 vartextNode=document.createTextNode(string(conter)) observer.observe(textNode,{ characterData:true }) timerFunc=()=>{ counter=(counter+1)%2 textNode.data=String(counter) } }else{ timerFunc=()=>{ setTimeout(nextTickHandler,0)//重点 } }
这一段我们可以看到标注的三个点表明在不同浏览器环境下使用Promise,MutationObserver或setTimeout(fn,0)来执行nextTickHandler
functionnextTickHandler(){ pending=false constcopies=callbacks.slice(0)//复制 callbacks.length=0//清空 for(leti=0;inextTickHandler就是把我们之前放入callbacks的()=>{//todo...}在当前tasks内执行。
写一个简单的nextTick
源码可能比较绕,我们自己写一段简单的nextTick
constsimpleNextTick=(function(){ letcallbacks=[] lettimerFunc returnfunctionqueueNextTick(cb){ callbacks.push(()=>{//给callbacks推入cb() cb() }) timerFunc=()=>{ returnPromise.resolve().then(()=>{ constfn=callbacks.shift() fn() }) } timerFunc()//执行timerFunc,返回到是一个Promise } })() simpleNextTick(()=>{ setTimeout(console.log,3000,'nextTick') })我们可以从这里看出nextTick的原理就是返回出一个Promise,而我们todo的代码在这个Promise中执行,现在我们还可以继续简化
constsimpleNextTick=(function(){ returnfunctionqueueNextTick(cb){ timerFunc=()=>{ returnPromise.resolve().then(()=>{ cb() }) } timerFunc() } })() simpleNextTick(()=>{ setTimeout(console.log,3000,'nextTick') })直接写成这样。
constsimpleNextTick=functionqueueNextTick(cb){ timerFunc=()=>{ returnPromise.resolve().then(()=>{ cb() }) } timerFunc() } simpleNextTick(()=>{ setTimeout(console.log,3000,'nextTick') })这次我们把自执行函数也简化掉
constsimpleNextTick=functionqueueNextTick(cb){ returnPromise.resolve().then(cb) } simpleNextTick(()=>{ setTimeout(console.log,3000,'nextTick') })现在我们直接简化到最后,现在发现nextTick最核心的内容就是Promise,一个microtask。
现在我们回到vue的nextTickAPI官方示例
{{message}}
原来在vue内数据的更新后dom更新是要在下一个事件循环后执行的。
nextTick的使用原则主要就是解决单一事件更新数据后立即操作dom的场景。
既然我们知道了nextTick核心是利用microTasks,那么我们把简化过的nextTick和开头的sleep函数对照一下。
constsimpleNextTick=functionqueueNextTick(cb){ returnPromise.resolve().then(cb) } simpleNextTick(()=>{ setTimeout(console.log,3000,'nextTick')//也可以换成ajax请求 })
functionsleep(ms){ returnnewPromise(resolve=>setTimeout(resolve,ms)//也可以换成ajax请求 } asyncfunctiononeTick(ms){ console.log('start') awaitsleep(ms) console.log('end') } oneTick(3000)
我们看出nextTick和我么写的oneTick的执行结果是那么的相似。区别只在于nextTick是把callback包裹一个Promise返回并执行,而oneTick是用await执行一个Promise函数,而这个Promise有自己包裹的webapi函数。
那在用ajax请求的时候我们是不是直接这样使用axios可以返回Promise的库
asyncfunctiongetData(){ constdata=awaitaxios.get(url) //操作data的数据来改变dom returndata }
这样也可以达到同nextTick同样的作用
最后我们也可以从源码中看出,当浏览器环境不支持Promise时可以使用MutationObserver或setTimeout(cb,0)来达到同样的效果。但最终的核心是microTask
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。