面试备战录

58 阅读4分钟

1、computed 和 watch 有什么区别?内部实现方式?

答:computed用于声明式派生状态,有缓存且惰性求值;内部实现是基于lazy effect,当依赖变化时才重新计算。watch用于侦听响应式数据变化执行副作用逻辑,无缓存,回调每次变化都会触发;内部实现是依赖effect,每当依赖触发trigger,就调用回调函数。computed侧重“值的计算和缓存”,watch侧重“执行副作用”。

  • computed:派生状态(根据其他响应式数据计算出新值),返回计算后的值(ref);惰性求值,只有访问.value时才计算;有缓存,依赖没变时不重新计算;场景UI显示、模板中绑定派生值。
  • watch:观察响应式数据变化,执行副作用逻辑;调用回调函数,不返回值;不惰性,监听立即生效;无缓存,每次触发回调都执行;场景异步请求、数据存储、动画、调试日志。

内部实现原理

  1. computed
  • 本质:带缓存的effect + lazy getter
const count = ref(1);
const double = computed(() => count.value * 2);
  • 内部做了:
    • 创建一个effect,标记为lazy,不立即执行。
    • .value被访问时,执行getter
      • 执行effect内部函数,依赖收集count
      • 保存计算结果到缓存。
    • 当依赖数据count变化时:
      • 触发effectscheduler,把缓存标记为“脏”,下次访问.value会重新计算。
  • 关键特点:
    • 惰性:只有访问才计算。
    • 缓存:依赖没变不重复计算。
  1. watch
  • 本质:侦听effect的回调。
watch(count, (newVal, oldVal) => {
  console.log(newVal, oldVal);
});
  • 内部做了:
    • 创建一个effect,不惰性,立即订阅count
    • 当依赖值变化时,调用scheduler:执行你提供的回调函数(newVal, oldVal) => {}
    • 可以通过选项{ immediate: true/false }控制是否立即执行一次。
  • 关键特点:
    • 触发回调,不返回值。
    • 无缓存,每次变化都执行回调。
    • 可监听多个数据、嵌套对象。

注:在computedwatch内不要修改其依赖数据,否则会造成死循环

2、Proxy 和 Reflect 在响应式中的应用?为何 Vue3 抛弃了 defineProperty?

答:Vue2Object.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/setProxyget/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 APIsetup()中:执行时机比组件实例创建还早。所以this还没有指向组件实例,访问时就是undefined。 所以不要在setup里使用this,而是通过返回对象暴露需要的变量和方法。
  • 业务逻辑拆分问题
    • Vue2常见做法:methods、computed、watch写在一起,但当逻辑复杂时就变得臃肿。
    • Vue3提供了几种 拆分业务逻辑 的方式:
      • 组合函数:把逻辑封装成 自定义 hooks(组合函数)。这样就能把复杂逻辑拆到单独文件里,可以像React Hooks一样复用。
      • provide / inject:用于跨层级组件通信,把全局逻辑注入子组件。
      • 状态管理(Pinia / Vuex):如果逻辑是全局性的,比如用户信息、主题,就可以抽到Pinia管理。