Vue中的Vue.nextTick的异步怎么实现
导读:本文共5648字符,通常情况下阅读需要19分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 一、nextTick小测试你真的了解nextTick吗?来,直接上题~<template><divid="app"><pref="name">{{name}}</p... ...
目录
(为您整理了一些要点),点击可以直达。
一、nextTick小测试
你真的了解nextTick吗?来,直接上题~
<template><divid="app"><pref="name">{{name}}</p><button@click="handleClick">修改name</button></div></template><script>exportdefault{name:'App',data(){return{name:'井柏然'}},mounted(){console.log('mounted',this.$refs.name.innerText)},methods:{handleClick(){this.$nextTick(()=>console.log('nextTick1',this.$refs.name.innerText))this.name='jngboran'console.log('synclog',this.$refs.name.innerText)this.$nextTick(()=>console.log('nextTick2',this.$refs.name.innerText))}}}</script>
请问上述代码中,当点击按钮“修改name”时,'nextTick1'
,'sync log'
,'nextTick2'
对应的this.$refs.name.innerText
分别会输出什么?注意,这里打印的是DOM的innerText~(文章结尾处会贴出答案)
如果此时的你有非常坚定的答案,那你可以不用继续往下看了~但如果你对自己的答案有所顾虑,那不如跟着我,接着往下看。相信你看完,不需要看到答案都能有个肯定的答案了~!
二、nextTick源码实现
源码位于core/util/next-tick中。可以将其分为4个部分来看,直接上代码
1. 全局变量
callbacks
队列、pending
状态
constcallbacks=[]//存放cb的队列letpending=false//是否马上遍历队列,执行cb的标志
2. flushCallbacks
遍历callbacks执行每个cb
functionflushCallbacks(){pending=false//注意这里,一旦执行,pending马上被重置为falseconstcopies=callbacks.slice(0)callbacks.length=0for(leti=0;i<copies.length;i++){copies[i]()//执行每个cb}}
3. nextTick
的异步实现
根据执行环境的支持程度采用不同的异步实现策略
lettimerFunc//nextTick异步实现fnif(typeofPromise!=='undefined'&&isNative(Promise)){//Promise方案constp=Promise.resolve()timerFunc=()=>{p.then(flushCallbacks)//将flushCallbacks包装进Promise.then中}isUsingMicroTask=true}elseif(!isIE&&typeofMutationObserver!=='undefined'&&(isNative(MutationObserver)||MutationObserver.toString()==='[objectMutationObserverConstructor]')){//MutationObserver方案letcounter=1constobserver=newMutationObserver(flushCallbacks)//将flushCallbacks作为观测变化的cbconsttextNode=document.createTextNode(String(counter))//创建文本节点//观测文本节点变化observer.observe(textNode,{characterData:true})//timerFunc改变文本节点的data,以触发观测的回调flushCallbackstimerFunc=()=>{counter=(counter+1)%2textNode.data=String(counter)}isUsingMicroTask=true}elseif(typeofsetImmediate!=='undefined'&&isNative(setImmediate)){//setImmediate方案timerFunc=()=>{setImmediate(flushCallbacks)}}else{//最终降级方案setTimeouttimerFunc=()=>{setTimeout(flushCallbacks,0)}}
这里用个真实案例加深对
MutationObserver
的理解。毕竟比起其他三种异步方案,这个应该是大家最陌生的constobserver=newMutationObserver(()=>console.log('观测到文本节点变化'))consttextNode=document.createTextNode(String(1))observer.observe(textNode,{characterData:true})console.log('scriptstart')setTimeout(()=>console.log('timeout1'))textNode.data=String(2)//这里对文本节点进行值的修改console.log('scriptend')
知道对应的输出会是怎么样的吗?
script start
、script end
会在第一轮宏任务中执行,这点没问题setTimeout
会被放入下一轮宏任务执行MutationObserver
是微任务,所以会在本轮宏任务后执行,所以先于setTimeout
结果如下图:
4. nextTick
方法实现
cb
、Promise
方式
exportfunctionnextTick(cb?:Function,ctx?:Object){let_resolve//往全局的callbacks队列中添加cbcallbacks.push(()=>{if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){//这里是支持Promise的写法_resolve(ctx)}})if(!pending){pending=true//执行timerFunc,在下一个Tick中执行callbacks中的所有cbtimerFunc()}//对Promise的实现,这也是我们使用时可以写成nextTick.then的原因if(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}
深入细节,理解
pending
有什么用,如何运作?
案例1,同一轮Tick中执行2次$nextTick
,timerFunc
只会被执行一次
this.$nextTick(()=>console.log('nextTick1'))this.$nextTick(()=>console.log('nextTick2'))
用图看看更直观?
三、Vue组件的异步更新
这里如果有对Vue组件化、派发更新不是十分了解的朋友,可以先戳这里,看图解Vue响应式原理了解下Vue组件化和派发更新的相关内容再回来看噢~
Vue的异步更新DOM其实也是使用nextTick
来实现的,跟我们平时使用的$nextTick其实是同一个~
这里我们回顾一下,当我们改变一个属性值的时候会发生什么?
根据上图派发更新过程,我们从watcher.update开时讲起,以渲染Watcher为例,进入到queueWatcher
里
1. queueWatcher
做了什么?
//用来存放Wathcer的队列。注意,不要跟nextTick的callbacks搞混了,都是队列,但用处不同~constqueue:Array<Watcher>=[]functionqueueWatcher(watcher:Watcher){constid=watcher.id//拿到Wathcer的id,这个id每个watcher都有且全局唯一if(has[id]==null){//避免添加重复wathcer,这也是异步渲染的优化做法has[id]=trueif(!flushing){queue.push(watcher)}if(!waiting){waiting=true//这里把flushSchedulerQueue推进nextTick的callbacks队列中nextTick(flushSchedulerQueue)}}}
2. flushSchedulerQueue
做了什么?
functionflushSchedulerQueue(){currentFlushTimestamp=getNow()flushing=trueletwatcher,id//排序保证先父后子执行更新,保证userWatcher在渲染Watcher前queue.sort((a,b)=>a.id-b.id)//遍历所有的需要派发更新的Watcher执行更新for(index=0;index<queue.length;index++){watcher=queue[index]id=watcher.idhas[id]=null//真正执行派发更新,render->update->patchwatcher.run()}}
最后,一张图搞懂组件的异步更新过程
四、回归题目本身
相信经过上文对nextTick源码的剖析,我们已经揭开它神秘的面纱了。这时的你一定可以坚定地把答案说出来了~话不多说,我们一起核实下,看看是不是如你所想!
1、如图所示,mounted
时候的innerText是“井柏然”的中文
2、接下来是点击按钮后,打印结果如图所示
没错,输出结果如下(意不意外?惊不惊喜?)
sync log 井柏然
nextTick1 井柏然
nextTick2 jngboran
下面简单分析一下每个输出:
this.$nextTick(()=>console.log('nextTick1',this.$refs.name.innerText))this.name='jngboran'console.log('synclog',this.$refs.name.innerText)this.$nextTick(()=>console.log('nextTick2',this.$refs.name.innerText))
sync log
:这个同步打印没什么好说了,相信大部分童鞋的疑问点都不在这里。如果不清楚的童鞋可以先回顾一下EventLoop,这里不多赘述了~nextTick1
:注意其虽然是放在$nextTick
的回调中,在下一个tick执行,但是他的位置是在this.name = 'jngboran'
的前。也就是说,他的cb会比App组件的派发更新(flushSchedulerQueue
)更先进入队列,当nextTick1
打印时,App组件还未派发更新,所以拿到的还是旧的DOM值。nextTick2
就不展开了,大家可以自行分析一下。相信大家对它应该是最肯定的,我们平时不就是这样拿到更新后的DOM吗?最后来一张图加深理解
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
Vue中的Vue.nextTick的异步怎么实现的详细内容,希望对您有所帮助,信息来源于网络。