一句话总结
v-if 是真实的条件渲染——切换时销毁/重建组件(惰性,初始为 false 时不渲染);v-show 只是 display 切换——初始就渲染,频繁切换开销小。computed 有缓存(依赖不变不重算),watch 无缓存(数据变化就执行,适合异步/开销大的副作用)。
初级理解
v-if vs v-show:v-if 是"真条件",元素不会渲染到 DOM;v-show 总是渲染,靠切换 CSS 控制显隐。
| 对比项 | v-if | v-show |
|---|---|---|
| 编译产物 | 三元表达式/分支 | display: none |
| 初始渲染 | false 时完全不渲染(惰性) | 始终渲染 |
| 切换开销 | 销毁/重建,大 | 样式切换,极小 |
| 适用场景 | 很少切换、条件分支少 | 频繁切换(Tab、开关) |
computed vs watch:computed 是"由已有数据算出的新数据",必须有返回值;watch 是"数据变化时执行一段逻辑",适合异步请求、复杂副作用。
中级深入
生命周期执行顺序:创建阶段 from 父到子,mounted 从子到父(子组件先挂载完成)。
v-for 为什么要加 key:key 是节点的"身份证"。diff 算法用 key 判断新旧节点是否可复用。用 index 作 key 时,列表中间插入/删除元素会导致后续节点 key 全部变化,引发不必要的重新渲染,且带输入框/勾选状态的组件会出现状态错乱。
nextTick 的原理:Vue 的 DOM 更新是异步批量的——修改数据后不会立即更新 DOM,而是把更新任务放入微任务队列,本轮事件循环结束后统一执行。nextTick 就是把回调追加到这些更新任务之后,保证拿到的是更新后的 DOM。
高级拓展
computed 的缓存机制:内部创建一个 lazy 的 Watcher/Effect——初始不执行,首次访问才计算并把结果缓存;依赖变化时只是把 dirty 标记为脏,下次访问才重算。所以 computed 不依赖的响应式数据变化不会触发重算。
watch 的进阶选项:
- deep: true:深度侦听对象内部变化(性能开销大,Vue3 中侦听 reactive 对象默认隐式开启且无法关闭)
- immediate: true:创建时立即执行一次(替代 mounted 里的重复调用)
- flush: 'post':回调在 DOM 更新后执行(等价于 watchPostEffect)
事件与按键修饰符的实现:@click.stop 编译为 withModifiers 包装函数,本质是高阶函数在真实事件触发前拦截处理,并不依赖任何运行时魔法。
实战场景
场景一:Tab 频繁切换用 v-if 导致卡顿
场景二:index 作 key 导致勾选状态错乱
场景三:搜索联想用 watch 而不是 computed
面试模拟
Q:computed 和 methods 的区别?
A:computed有缓存,依赖不变时多次访问只算一次;methods 每次调用都重新执行。模板里同一数据多处使用时 computed 明显更优。另外 computed 不能传参(需要传参就封装成函数返回 computed)。
Q:Vue2 和 Vue3 的响应式为什么不同?v-if/v-for 优先级有变化吗?
A:Vue2 用 Object.defineProperty 只能劫持已存在属性,新增/删除/数组索引监听不到,需要 $set;Vue3 用 Proxy 代理整个对象,天然支持增删和索引。优先级方面:Vue2 中 v-for 高于 v-if,Vue3 中 v-if 高于 v-for,混写时语义相反,官方也不推荐同节点混用。
Q:nextTick 在 Vue2 和 Vue3 中的实现有什么不同?
A:原理一致——把回调放到微任务里排队。Vue2 有一套优雅降级(Promise → MutationObserver → setImmediate → setTimeout);Vue3 直接用 Promise.then,因为目标环境已支持 Promise。