首页>>前端>>Vue->vue2.x 中的数据异步更新和 nextTick 方法解析

vue2.x 中的数据异步更新和 nextTick 方法解析

时间:2023-11-29 本站 点击:0

前言

众所周知,vue 中的更新时异步的,比如 this.msg = xxx,你看起来他是立马更新了,其实并没有。它会异步执行,接下来就来看看怎么实现的吧。

先上图

首先从数据改动开始说起

调用this.msg = xxx 数据发生变更

在数据初始化阶段已经收集了依赖的watcher 到 dep 中, 执行 dep.notify 通知watcehr变更

notify 方法遍历调用 所有以来的watcher 的update 方法,把当前watcher 实例放入 queueWatcher 函数中执行,接下来就是异步更新的关键了,看代码

queueWatcher 函数代码 在 src\core\observer\scheduler.js 主要作用:把当前 watcher 实例添加到一个 queue 中

exportfunctionqueueWatcher(watcher:Watcher){//拿到watcher的唯一标识constid=watcher.id//无论有多少数据更新,相同的watcher只被压入一次//我理解这就是为什么在一次操作中,多次更改了变量的值,但是只进行了一次页面更新的原因,//同一变量依赖它的watcher是一定的,所以已经存在了就不再放进watcher队列中了,也不会走后面的逻辑if(has[id]==null){//缓存当前的watcher的标识,用于判断是否重复has[id]=true//如果当前不是刷新状态,直接入队if(!flushing){queue.push(watcher)}else{//ifalreadyflushing,splicethewatcherbasedonitsid//ifalreadypastitsid,itwillberunnextimmediately.//此处能走到这儿,说明flushSchedulerQueue函数被执行了watcher队列已经正在开始被更新了,//并且在执行某个watcher.run方法的时候又触发的数据响应式更新,重新触发了queueWatcher//因为在执行的时候回有一个给watcher排序的操作,所以,当watcher正在更新时已经是排好顺序了的,此时需要插入到特定的位置,保持watcher队列依然是保持顺序的leti=queue.length-1while(i>index&&queue[i].id>watcher.id){i--}queue.splice(i+1,0,watcher)}//queuetheflush//waiting表示当前的flushSchedulerQueue还没有被执行,因为还没有重置状态,waiting仍然为true//所以waiting的意义就是表明是否执行了flushSchedulerQueue,if(!waiting){waiting=true//直接同步刷新队列if(process.env.NODE_ENV!=='production'&&!config.async){//同步执行flushSchedulerQueue()return}//把更新队列函数放到异步队列中nextTick(flushSchedulerQueue)}}}

flushSchedulerQueue 代码在相同目录下 // 主要作用: 遍历执行每一个 watcher 的 run 方法,进而实现数据和视图的更新,并在执行完所有的 方法之后,重置状态,表示正在刷新队列的 flushing, 表示 watcher 是否存在的 has,表示是否需要执行 nexttick 的 waiting

functionflushSchedulerQueue(){//当方法被执行时,设置为正在刷新状态,以示可以继续执行nextTick方法flushing=true//把队列中的watcher排个序,/***排序的作用:(此句照搬照抄而来)*1.保证父组件的watcher比子组件的watcher先更新,因为父组件总是先被创建,子组件后被创建*2.组件用户的watcher在其渲染watcher之前执行。*3.如果一个组件在其父组件执行期间被销毁了,会跳过该子组件。*/queue.sort((a,b)=>a.id-b.id)//中间略去若干代码...//遍历queue中存的所有的watcher,执行run方法更新for(index=0;index<queue.length;index++){watcher=queue[index]watcher.run()}//因为queue是在一个闭包中,所以当遍历执行完毕了,就把队列清空queue.length=0;//has是判断当前watcher是否重复,作为是否把watcher放进queue的依据//此时已经执行完了queue中的所有watcher了,之前已经执行过的watcher如果发生了变更,可以重新加入了has={}//waiting是判断是否执行nextTick的标识,当前的刷新队列已经执行完毕了,说以,可以设置为false了,执行下一轮的的添加异步事件队列的方法//flushing是判断是否当前异步事件正在执行的标志,当前更新完毕,作为判断watcher入队的形式waiting=flushing=false}

nextTick 方法 源码src\core\util\next-tick.js

exportfunctionnextTick(cb?:Function,ctx?:Object){let_resolve//把执行更新操作之后的回调函数添加到队列里//用trycatch包装一下传进来的函数,避免使用$nextTick时,传入的回调函数出错能够及时的捕获到//只要执行了nextTick函数,就把回调函数添加到回调列表里//这里的cb回调函数就是flushSchedulerQueue函数,里面执行了queue中存放的所有的watcher.run方法callbacks.push(()=>{if(cb){try{cb.call(ctx)}catch(e){handleError(e,ctx,'nextTick')}}elseif(_resolve){_resolve(ctx)}})//通过pending来判断是否需要向任务队列中添加任务//如果上一个清空回调列表的当flushCallbacks函数还在任务队列中,就不往任务队列中添加//第一次执行时,就默认就添加一个进任务队列,一旦添加进任务队列,就表明暂时不在需要往任务队列中添加flush函数//当执行了上一个flushCallbacks函数的时候,pending修改为false,表明可以重新添加一个清空回调列表的flush函数到任务队列了if(!pending){pending=true//这里是调用清空callbacks数组中方法,并执行的函数,timerFunc()}//$flow-disable-line//判断当前环境是否支持promise,如果支持的话,可以返回一个期约对象,if(!cb&&typeofPromise!=='undefined'){returnnewPromise(resolve=>{_resolve=resolve})}}

timerFunc() 方法,主要是做一些降级操作,实现异步的关键

timerFunc=()=>{Promise.resolve().then(flushCallbacks)}//如果当前环境不支持的话,会进行一定的降级操作,直到最后,用宏任务settimeout来处理

看看 flushCallbacks, 任务就是执行了所有的 callbacks函数

functionflushCallbacks(){//如果开始执行了flushCallbacks说明,当前的异步任务已经为空了,如果此时再nextTick方法会添加新的任务进去了pending=false//拷贝一份callbacks中的所有回调函数,用于执行constcopies=callbacks.slice(0)//随即删除所有callbackscallbacks.length=0//当微任务队列中的flushCallbacks添加到执行栈中了,就执行callbacks中的所有的函数//也就是调用执行每一个flushSchedulerQueue函数,然后遍历执行每一个函数for(leti=0;i<copies.length;i++){copies[i]()}}

基本关键变量的作用

waiting: 变量,作为是否执行 nextTick ,添加 flushSchedulerQueue 方法的关键,标志着 callbacks 中 是否有 flushSchedulerQueue 方法, 比如同一个变量的改变,可能会影响多个 watcher,因为执行 flushSchedulerQueue 是异步的,遍历dep.update 先把所有的 watcher 都放入到 queue 中,也才只执行了一次 nextTick,callbacks中也只有一个方法。虽然当第一次方如 watcher 时就会执行 nexttick 把 flushSchedulerQueue方法放入callbacks 中,看起来好像已经要执行了,但是因为 queue 是闭包变量,所以,后续的变量仍然可以添加queue 中,

flushing:: 表示是否正在执行 flushSchedulerQueue 方法,如果是正在执行更新方法的话,对向已经排好序的 watcher 队列中添加新的 watcher,需要把新 watcher 插入到排好序的指定的位置,这也就是为什么遍历 watdher 那块儿会 直接使用 queue.length 的原因,这个长度会发生变化。

pending:: pending 是决定是否把更新 callbacks 数组的方法放入异步队列的关键,保证了异步队列中只有一个清空callbacks 的任务, 也就解释了,连续手动执行多个 $nextTick 方法不会立即执行,也还是会把他们的回调 放入 callbacks 中,然后等到任务都执行完毕了,一下把所有的 回调函数都执行掉。

参考

vue 源码

https://juejin.cn/post/6951568091893465102


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Vue/828.html