Sunday 的面试指南

Vue 3 响应式原理是什么?Proxy、ref 和 reactive 有什么区别?

🧑‍💻 面试官:Vue 3 为什么改一个值,页面就会跟着变?

🙋‍♂️ 我:它用 Proxy 拦截对象读写,知道数据变化以后更新页面。

🧑‍💻 面试官:Proxy 只知道属性被读取了,怎么知道要更新哪块内容?

🙋‍♂️ 我:还需要在读取时记录依赖,修改时通知对应的执行逻辑。

🧑‍💻 面试官:那我把 count 解构出来,再修改 count,原对象也会跟着变吗?ref 为什么还需要 .value?

响应式要抓住「谁读过这个值」:拦截读写只是入口,记录读取关系、通知依赖重新执行,才把数据和页面连起来。

面试速答(60 秒版)

Vue 3 的 reactive 为对象创建响应式代理。依赖执行时读取属性,系统记录这段逻辑依赖哪个属性;属性改变后,再通知相关依赖更新。

Proxy 负责拦截操作,依赖收集和触发机制负责建立对应关系。不是任意一次读取都会登记,也不是每次修改都让整页立即重新渲染,组件更新还会经过调度。

ref 则用带 .value 的包装,让基本类型和对象都能作为响应式值使用。reactive 更适合直接组织对象属性,ref 也方便整体替换值。

解构 reactive 中的基本类型属性,会得到当时的普通值,后续访问不再经过原代理。需要保留与原属性的连接时,可以使用 toRef 或 toRefs。对象属性的解构要另外看它是否仍然指向响应式对象。

Vue 响应式:读取时记录,修改时通知

知识点详解:拦截读写,怎样变成更新页面?

先建立一个具体关系

假设页面显示 state.count。组件渲染时,会读取这个属性。

Vue 在这一轮渲染逻辑执行期间,能够知道当前有哪些属性被使用。于是它记下来:“这个渲染逻辑需要 count。”

以后 count 改变,就能找到需要重新执行的逻辑。如果另一段逻辑只读 name,它不应该仅仅因为 count 改了就成为 count 的直接依赖。

这就是“依赖收集”要解决的事情。只说 Proxy 可以拦截,还没有说明页面与属性怎样建立关系。

读取时记录,修改时通知

可以把主要过程理解成三步:

  1. 某段需要跟踪的逻辑开始执行,成为当前活跃的依赖。
  2. 它读取代理属性,系统记录对象、属性与依赖之间的关系。
  3. 属性改变后,系统找到相关依赖,触发或调度它们重新执行。

Vue 官方响应式原理给出了 track 与 trigger 的教学示意。实际实现还需要处理嵌套执行、过期依赖清理、数组与集合等情况,不能拿几行伪代码当完整源码。

如果代码只在普通函数里随手读了一次属性,而没有处于依赖收集的执行过程中,就不能认为这个函数以后会自动再次运行。

reactive 和 ref,不只是对象与数字的区别

reactive 返回对象代理。对属性读写,才能经过代理的拦截。如果还拿原始对象直接修改,就绕开了这条入口。

ref 返回一个带 .value 的容器,对这个值的读取与修改也能被跟踪。它可以保存数字,也可以保存对象;对象值通常会经过响应式转换,shallowRef 等 API 则有不同约定。

因此,“对象一定用 reactive,数字一定用 ref”只是一种简化习惯,不是能力边界。

如果经常需要替换整个值,比如请求完成后把结果列表整体换掉,ref 的 .value = 新值 往往比较直接。reactive 则更适合保留一个代理对象,修改其中属性。

解构后为什么有时不更新?

看一个 Vue 3 局部示例:

import { reactive, toRef } from "vue";

const state = reactive({ count: 0 });
const { count } = state;          // 当时读出的普通数字
const linkedCount = toRef(state, "count");

state.count = 2;
console.log(count);               // 0
console.log(linkedCount.value);   // 2

linkedCount.value = 3;
console.log(state.count);         // 3

普通数字被取出来以后,变量与原属性已经不是同一个可追踪入口。toRef 则保留了指向该属性的连接。

不过,如果解构的是一个响应式对象引用,继续修改那个对象的属性,仍然可能触发响应式。问题是“变量绑定是否与原属性相连”,不能简单背成“只要解构就彻底没响应式”。

这里的代码针对 Vue,不提供不同语言里名为 Proxy 的对象来冒充同一机制。

普通解构与 toRef,连接不同

数据改变了,DOM 为什么还没立即改变?

响应式通知和 DOM 已更新,是两个时间点。

Vue 通常把组件更新放进调度队列,合并同一轮中的多个变化,避免每修改一个值就立刻重复更新。需要在更新后读取 DOM,可以使用 nextTick 等待相应更新完成。

computed 则是在响应式系统上建立派生值。它会跟踪所读取的依赖,并按缓存与求值规则工作,不是普通函数换了一个名字。

讲到这里,才能从数据读写一路解释到组件更新,而不是只回答“Vue 3 用了 Proxy”。

面试官继续追问

Proxy 能直接代理数字吗?

不能直接以基本类型作为 Proxy 的目标对象。ref 提供了包装入口,使数字等值也能进入响应式系统。

reactive 返回值等于原对象吗?

引用身份不同。代理虽然表现得像原对象,但 proxy === original 通常为 false。项目中最好统一通过代理操作,避免混用导致追踪问题。

模板里为什么常常不用写 .value?

Vue 模板提供了 ref 解包规则,但规则有具体适用条件。JavaScript 代码中操作 ref,通常仍然使用 .value,不能把模板便利理解成容器不存在了。

面试速记卡

  • Proxy:拦截操作,不独自完成整套响应式。
  • 读取阶段:在依赖执行时记录对象、属性与依赖的关系。
  • 修改阶段:通知相关依赖,组件更新还要经过调度。
  • ref 与 reactive:容器值和对象代理,都有适合的组织方式。
  • 解构边界:基本类型会断开属性连接,可用 toRef 保留连接。
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历