Vue中的Vue.nextTick的异步怎么实现(vue,vue.nexttick,编程语言)

时间:2024-05-02 18:28:21 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

Vue中的Vue.nextTick的异步怎么实现

一、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 startscript end会在第一轮宏任务中执行,这点没问题

    • setTimeout会被放入下一轮宏任务执行

    • MutationObserver是微任务,所以会在本轮宏任务后执行,所以先于setTimeout

  • 结果如下图:
    Vue中的Vue.nextTick的异步怎么实现


4. nextTick方法实现

cbPromise方式

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次$nextTicktimerFunc只会被执行一次

this.$nextTick(()=>console.log('nextTick1'))this.$nextTick(()=>console.log('nextTick2'))
  • 用图看看更直观?

Vue中的Vue.nextTick的异步怎么实现


三、Vue组件的异步更新

这里如果有对Vue组件化派发更新不是十分了解的朋友,可以先戳这里,看图解Vue响应式原理了解下Vue组件化和派发更新的相关内容再回来看噢~

Vue的异步更新DOM其实也是使用nextTick来实现的,跟我们平时使用的$nextTick其实是同一个~

这里我们回顾一下,当我们改变一个属性值的时候会发生什么?

Vue中的Vue.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()}}
  • 最后,一张图搞懂组件的异步更新过程

Vue中的Vue.nextTick的异步怎么实现


四、回归题目本身

相信经过上文对nextTick源码的剖析,我们已经揭开它神秘的面纱了。这时的你一定可以坚定地把答案说出来了~话不多说,我们一起核实下,看看是不是如你所想!

1、如图所示,mounted时候的innerText是“井柏然”的中文

Vue中的Vue.nextTick的异步怎么实现

2、接下来是点击按钮后,打印结果如图所示

Vue中的Vue.nextTick的异步怎么实现

  • 没错,输出结果如下(意不意外?惊不惊喜?)

    • 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吗?

  • 最后来一张图加深理解

Vue中的Vue.nextTick的异步怎么实现

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:Vue中的Vue.nextTick的异步怎么实现的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:Python中的对象析构函数del怎么用下一篇:

6 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18