浅谈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;i
nextTickHandler就是把我们之前放入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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。