1、computed 和 watch 有什么区别?内部实现方式?
答:computed用于声明式派生状态,有缓存且惰性求值;内部实现是基于lazy effect,当依赖变化时才重新计算。watch用于侦听响应式数据变化执行副作用逻辑,无缓存,回调每次变化都会触发;内部实现是依赖effect,每当依赖触发trigger,就调用回调函数。computed侧重“值的计算和缓存”,watch侧重“执行副作用”。
computed:派生状态(根据其他响应式数据计算出新值),返回计算后的值(ref);惰性求值,只有访问.value时才计算;有缓存,依赖没变时不重新计算;场景UI显示、模板中绑定派生值。watch:观察响应式数据变化,执行副作用逻辑;调用回调函数,不返回值;不惰性,监听立即生效;无缓存,每次触发回调都执行;场景异步请求、数据存储、动画、调试日志。
内部实现原理:
computed
- 本质:带缓存的
effect + lazy getter
const count = ref(1);
const double = computed(() => count.value * 2);
- 内部做了:
- 创建一个
effect,标记为lazy,不立即执行。 .value被访问时,执行getter:- 执行
effect内部函数,依赖收集count。 - 保存计算结果到缓存。
- 执行
- 当依赖数据
count变化时:- 触发
effect的scheduler,把缓存标记为“脏”,下次访问.value会重新计算。
- 触发
- 创建一个
- 关键特点:
- 惰性:只有访问才计算。
- 缓存:依赖没变不重复计算。
watch
- 本质:侦听
effect的回调。
watch(count, (newVal, oldVal) => {
console.log(newVal, oldVal);
});
- 内部做了:
- 创建一个
effect,不惰性,立即订阅count。 - 当依赖值变化时,调用
scheduler:执行你提供的回调函数(newVal, oldVal) => {}。 - 可以通过选项
{ immediate: true/false }控制是否立即执行一次。
- 创建一个
- 关键特点:
- 触发回调,不返回值。
- 无缓存,每次变化都执行回调。
- 可监听多个数据、嵌套对象。
注:在
computed和watch内不要修改其依赖数据,否则会造成死循环
2、Proxy 和 Reflect 在响应式中的应用?为何 Vue3 抛弃了 defineProperty?
答:Vue2用Object.defineProperty劫持属性实现响应式,但它不能监听新增/删除属性、数组索引和length,且需要递归遍历,性能差。Vue3使用Proxy + Reflect,可以一次性代理整个对象,支持对象/数组的所有操作,按需劫持性能更好,并且代码更简洁。Reflect保证了Proxy内部操作和原生语义保持一致。
Vue2通过Object.defineProperty劫持对象属性的getter/setter来做依赖收集和更新通知:- 只能劫持已有属性:对象新增/删除属性无法监听 (
Vue.set / Vue.delete来补救)。数组的下标修改、length变化无法直接监听(Vue2通过重写数组方法hack)。 - 深层嵌套对象需要递归遍历:初始化时必须递归遍历所有属性,性能差。
- 只能劫持已有属性:对象新增/删除属性无法监听 (
Vue3直接用Proxy劫持整个对象,而不是单个属性。- 新增/删除属性可拦截
set / deleteProperty - 数组监听:原生支持(下标/length 都能拦截)
- 按需代理(惰性监听)
- 性能:懒代理,访问时才劫持,性能更好
- 可维护性:
API简洁,统一由Proxy拦截
- 新增/删除属性可拦截
- 为什么
Vue3里要配合Reflect?- 保持语义一致:
Reflect.get/set和Proxy的get/set handler参数完全一致,更简洁。 - 返回值语义正确:
Reflect.set会返回true/false,表示设置是否成功。直接用target[key] = value无法知道是否成功。 - 解决原型链问题:
Reflect会保证操作和JS内部一致,比如super调用、原型链继承场景下,行为和原生保持一致。
- 保持语义一致:
3、setup() 中访问不到 this?怎么解决业务逻辑拆分的问题?
答:在Vue3中,setup执行时机早于组件实例创建,所以不能使用this。推荐通过组合式API拆分业务逻辑,例如自定义 hooks(组合函数),provide/inject,或者使用状态管理(如Pinia),这样既能避免 this 使用限制,又能让逻辑更加复用和模块化。
- 为什么
setup()中访问不到this- 在
Options API里(Vue2 风格),组件实例会被挂载,this指向当前组件实例,可以拿到data、props、methods。 - 在
Composition API的setup()中:执行时机比组件实例创建还早。所以this还没有指向组件实例,访问时就是undefined。 所以不要在setup里使用this,而是通过返回对象暴露需要的变量和方法。
- 在
- 业务逻辑拆分问题
Vue2常见做法:methods、computed、watch写在一起,但当逻辑复杂时就变得臃肿。Vue3提供了几种 拆分业务逻辑 的方式:- 组合函数:把逻辑封装成 自定义 hooks(组合函数)。这样就能把复杂逻辑拆到单独文件里,可以像
React Hooks一样复用。 provide / inject:用于跨层级组件通信,把全局逻辑注入子组件。- 状态管理(
Pinia / Vuex):如果逻辑是全局性的,比如用户信息、主题,就可以抽到Pinia管理。
- 组合函数:把逻辑封装成 自定义 hooks(组合函数)。这样就能把复杂逻辑拆到单独文件里,可以像