在项目中调试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队列中

相关推荐
网络点点滴21 小时前
前端与后端的区别与联系
前端
EnCi Zheng21 小时前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen1 天前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技1 天前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人1 天前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实1 天前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha1 天前
三目运算符
linux·服务器·前端
晓晨的博客1 天前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 天前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 天前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化