一、前言
你将从本文了解到代码的执行时机,以及拓展Vue下computed / watch / nextTick执行时机的差异。
二、执行时机
1. JS 事件循环
JavaScript 是单线程语言,所有代码依靠事件循环 EventLoop调度执行,执行优先级严格固定:同步代码 > 微任务 > 异步任务
2. 同步代码
按照书写顺序从上到下依次执行,阻塞后续所有任务普通变量赋值、函数调用、逻辑运算、循环判断都属于同步代码只有当前执行栈所有同步代码全部执行完毕,才会进入微任务队列。
3. 微任务
同步执行栈清空后,会一次性清空当前微任务队列中的全部任务。
值得注意的是Promise的哪一部分是微任务,因为这里总有人会混淆。Promise构造函数内部的执行器代码,全部为同步代码,创建实例时就会立即从上至下执行,不存在异步延迟。只有当内部执resolve()或者reject(),修改 Promise 状态之后,后续链式调用的.then、.catch、.finally回调函数,才会被浏览器推入微任务队列排队等待。
console.log("开始"); new Promise((resolve, reject) => { console.log("Promise 构造函数内部代码"); resolve(); // 修改状态 }); .then(() => { console.log("Promise.then 微任务回调"); }) console.log("结束");3. 异步任务
优先级最低,其会先交由浏览器后台线程进行监听与处理,不会阻塞主线程。
但回调函数的执行时机,必须等待同步 + 当前微任务队列中的全部任务执行完成。
这也就是setTimeout即使设置为0秒,也无法同步执行的原因。
console.log('1 同步代码开始'); setTimeout(() => { console.log('2 setTimeout'); }, 0); console.log('3 同步代码结束');三、Computed / Watch / NextTick
1. 三者各自的定义
computed属于同步执行,既不是微任务,也不是异步任务。执行时机早于 watch、早于 nextTick、早于 DOM 更新。
watch 默认运行在微任务阶段,本身不属于原生 JS 微任务,watch 跟随 Vue 异步更新调度,执行时序等价微任务。
nextTick 底层基于 Promise 封装,是标准微任务。
2. 为什么 watch 执行时机和微任务一致,但它不是原生微任务?
Vue 为了批量更新、减少重复渲染,会把数据变更触发的 watch、组件更新等逻辑,统一收集到内部调度队列。Vue 会借助原生微任务去做整体队列刷新,但 watch 本身只是队列里的普通回调,不是独立的原生微任务。
Vue 选择在浏览器微任务阶段统一刷新自身队列,所以 watch 看着和微任务一起执行、时序完全一致。
四、总结
只有理解好代码的执行时机,才能在复杂项目中高效修复 bug、顺畅调整需求。
因为复杂项目中,往往存在大量同步逻辑、异步操作与 Vue 响应式 API 交织,比如数据变更后DOM 未及时更新、watch 回调执行时机偏差导致的数据错乱、微任务与异步任务顺序颠倒引发的交互异常等问题,只有精准掌握执行时机,才能快速定位问题根源,避免逻辑混乱,确保代码执行符合预期。