【vue高频面试题】第 10 题:`watch` VS `watchEffect` 的区别是什么?触发时机有什么不同?

第 10 题:watch VS watchEffect 的区别是什么?触发时机有什么不同?


🎯 一、标准回答(你在面试应该先说的)

1. watch:显式侦听某个特定数据源

  • 需要指定侦听对象
  • 默认 懒执行(lazy) ,只有依赖发生变化才触发
  • 获取新旧值 (newValue, oldValue)
  • 可控制 flush 时机

示例:

javascript 复制代码
watch(count, (newVal, oldVal) => {
  console.log('count changed')
})

2. watchEffect:自动收集依赖,不需要指定监听对象

  • 未指定具体依赖,运行时自动收集依赖数据
  • 默认 立即执行一次(立即执行模式)
  • 获取不到旧值
  • 更适合副作用逻辑(比如接口、localStorage)

示例:

scss 复制代码
watchEffect(() => {
  console.log(count.value)
})

🎯 二、触发时机(超关键)

Vue3 的侦听器有 3 种触发时机(flush):

模式 时机 特点
pre(默认) 组件渲染前触发(同步) 不保证 DOM 最新
post 组件 DOM 更新后触发 可安全访问最新 DOM
sync 同步触发 更危险,一般不用

watch 和 watchEffect 的默认时机不同:

🔹 watch 默认:flush: 'pre'

  • 不保证 DOM 已更新
  • 一般用于数据逻辑,不用于 DOM 操作

🔹 watchEffect 默认:也是 flush: 'pre',但它会立即执行一次

  • 所以表现上像是"自动"、"立即"

🎯 三、最关键的区别(面试官想听的 4 点)

项目 watch watchEffect
依赖收集 手动指定 自动分析依赖
何时执行 默认仅数据变化触发 默认会先执行一次,之后依赖变化触发
能否拿 oldValue ✔ 可以 ❌ 不行
使用场景 监听明确数据 不明确依赖的副作用逻辑

🎯 四、什么时候用 watch?什么时候用 watchEffect?(场景记一下)

watch 适合

  • 明确监听某变量:countprops.id
  • 需要比较 oldValue / newValue
  • 有复杂条件逻辑
  • 想严格控制触发

watchEffect 适合

  • 自动根据依赖触发,不想手动声明监听项
  • 监听多个变量
  • 处理副作用:打印日志、请求 API、操作本地存储

例子:监听多个变量:

scss 复制代码
watchEffect(() => {
  console.log(a.value + b.value)
})

🎯 五、面试官常见追问(附高分答案)


追问 1:watchEffect 不会工作或一直触发,是为什么?

答案:因为 watchEffect 会自动追踪所有读取到的响应式变量。
如果在逻辑里访问了不必要的响应式数据,会导致频繁触发。

例子:

scss 复制代码
watchEffect(() => {
  console.log(stateA.value)
  console.log(stateB.value)  // 明明不想监听,却访问到了
})

追问 2:watchEffect 和 watchEffect(() => {}, { flush: 'post' }) 的区别?

post 能保证 DOM 已更新。

例如:

scss 复制代码
watchEffect(() => {
  console.log(el.value.offsetHeight) // 有可能是旧 DOM
}, { flush: 'post' })

追问 3:可以不用 watchEffect,全部用 watch 吗?

可以,但不推荐。

watchEffect 的优势:

  • 写法简单
  • 自动收集依赖
  • 更适合复杂副作用逻辑

追问 4:watchEffect 会不会比 watch 性能差?

会。

原因:

  • 它自动收集依赖
  • 每次执行都会重新收集依赖
  • 逻辑写不好容易死循环

所以 watchEffect 适合用在 轻量副作用


追问 5:onInvalidate 是干嘛用的?

比如你发了一个异步请求,依赖变化时需要取消之前的请求。

scss 复制代码
watchEffect((onInvalidate) => {
  const id = count.value
  const controller = new AbortController()

  fetch(`/api/${id}`, { signal: controller.signal })

  onInvalidate(() => controller.abort())
})

这是非常加分的高级用法。

相关推荐
fanruitian16 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo16 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk17 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
2501_9445255418 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
Bella的成长园地18 小时前
面试中关于 c++ async 的高频面试问题有哪些?
c++·面试
李白你好19 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
Abona19 小时前
C语言嵌入式全栈Demo
linux·c语言·面试
刘一说20 小时前
Vue 组件不必要的重新渲染问题解析:为什么子组件总在“无故”刷新?
前端·javascript·vue.js
徐同保21 小时前
React useRef 完全指南:在异步回调中访问最新的 props/state引言
前端·javascript·react.js
刘一说21 小时前
Vue 导航守卫未生效问题解析:为什么路由守卫不执行或逻辑失效?
前端·javascript·vue.js