在项目中调试vue2源码,watch,nextTick执行顺序梳理

前言

最近在公司排查bug得时候发现一个有关于watch和nextTick执行顺序得问题,原本我以为watch得触发会向微任务队列里push一条任务,nextTick也是一样,这样的话如果nextTick先执行,之后在触发watch得话会先执行nextTick得回调,然后在执行watch得监听函数,可是项目里得代码怎么调试执行顺序都和我想象得不一样,为了一谈究竟,搞清楚watch和nextTick之间到底有什么关系,我准备将vue2得源码打包出一份带有map得集成到项目中,来彻底搞懂这个流程。

打包vue2源码生成带有map文件vue.js

vue2得源码从git上clone下来之后,先执行install安装依赖 我得vue2版本是2.7.16,查看其package.json文件下得打包命令发现执行了一个js文件,查看这个build.js文件,其实做的事情很简单,就是读取一下所有得打包配置,然后使用rollup进行打包。

我得工程里面只用到了vue.js,所以我们可以把用不到得注释掉,只保留这一个文件即可,并把返回的配置项中output中添加一行sourcemap:true详情见下图

接下来还需要调整一下build.js文件让生成得map写入磁盘中

之后执行npm run build在dist目录下就生成了带有map得vue.js

将带有map得vue.js集成到项目中

我得项目是基于vue2得版本也是2.7.16,与打包出来得vue.js版本一致,之后使用我们自己编译出来的vue.js和vue.js.map替换项目中得vue文件,直接放对应得dist文件中即可

我得项目是基于vite构建得,本身能够加载node_modules中得map文件,但是由于vite构建存在缓存情况,可以查看下图目录 实际加载es模块得时候都是这个目录下面得,如果构建之后没有vue.js.map要执行一下vite --force清空.vite重新生成

之后在断点调试得时候就可以看到调用栈上得vue源码了,都是编译前得ts文件,我们就可以分析项目中代码运行watch和nextTick得真实执行流程了

watch与nextTick流程梳理

现在写两个简单组件,通过他们分析一下vue得执行流程

  1. 触发函数ccc得时候先改变了响应式数据testProps 父组件会将一个Wather放入queue队列中,这个Wather是渲染Wather,渲染Watcher会执行组件重新渲染,vue在组件渲染得时候会创建这个Wather并关联组件得响应式数据,当组件内得响应式数据发生改变得时候,会触发这个Wather,进行组件重新渲染
  1. 这个watcher并不会立刻执行而是将一个函数flushSchedulerQueue放到nextTick中,flushSchedulerQueue这个函数得作用就是执行queue这个队列

  2. 之后我们看下nextTick中做了什么,看他的代码就是把nextTick中得回调函数放到了一个callback数组中,之后如果没有pending就执行timerFunc()第一次执行得时候没有pending所以回直接执行这个函数

  1. 这个函数内部就是把flushCallbacks放到了微任务队列中
  1. 到目前为止我们梳理一下已经出现得两个队列,queue和callBacks以及两个刷新函数他们之间得关系如下图,wather1指渲染wather
  1. 之后在这个渲染周期中就没有其他操作了,按顺序执行微任务,先执行flushSchedulerQueue中的队列,先执行watcher1(渲染watcher)执行渲染函数渲染自己得组件,接着触发子组件得渲染,在渲染子组件得过程中发现,子组件监听得testProps值改变了,于是就将testProps得监听函数放入queue中,于是在执行flushSchedulerQueue得过程中queue得长度变化了,flushSchedulerQueue会执行完所有队列中所有得watcher。这也是vue中多次响应式数据变更但只有一次渲染得原理,其实多次变更都被收集到queue队列中,一个渲染周期中callbacks队列中只会有一个flushSchedulerQueue这个函数,保证了多个响应式数据改变,在同一个渲染周期内只渲染一次
  2. 刷新完watcher队列(flushSchedulerQueue函数执行完毕)之后执行nextTick得回调,将data得值改变,这个值也是响应式数据,触发流程和testProps一样,但是由于这个渲染周期中得flushSchedulerQueue函数已经执行完毕,所以waiting标记改为了false,导致这次得渲染watcher不会立刻执行,而是放到了微任务队列中,在本次微任务执行完毕之后在执行
  1. 此时得情况如下图所示,第一个微任务已经执行完毕

总结

在调试完毕之后我发现我之前存在得问题: watch 本身不会创建微任务,它只是把 watcher 放入 queue 队列,如果在nextTick之后有watcher被放入队列,那么这个watcher得执行并不一定会在nextTick得回调之后,具体要看flushSchedulerQueue在callbacks中得顺序

下图情况会先输出222;nextTick回调函数先被放入callbacks队列中

下图情况则会先输出两个watcher结果,在输出222,flushSchedulerQueue先被放到callbacks队列中

相关推荐
爱敲点代码的小哥2 小时前
json序列化和反序列化 和 数组转成json格式
java·前端·json
林太白2 小时前
2025 AI浪潮下的编程之路:我的天工项目与终身学习
前端·后端·trae
再花2 小时前
VitePress+Github Pages实现静态文档站
前端
Lsx_2 小时前
案例+图解带你一文读懂Svg、Canvas、Css、Js动画🔥🔥(4k+字)
前端·javascript·canvas
十一.3662 小时前
127-130 定时器的简介,切换图片练习,修改div移动练习,延时调用
前端·javascript·html
Jolyne_2 小时前
React下拉框接口请求hook封装
前端
狗头大军之江苏分军2 小时前
2025,我的"Vibe Coding"时刻:当 AI 成为我的编程搭档
前端
同学807962 小时前
🔥🔥Vue数字翻滚动画组件:让数据展示更具视觉冲击力
前端·vue.js
HashTang2 小时前
【AI 编程实战】第 5 篇:Pinia 状态管理 - 从混乱代码到优雅架构
前端·vue.js·ai编程