真实面经题目 · 原创解析
说说 Vue 的响应式原理吧
这题考察 Vue 响应式如何把数据读写、依赖收集和视图更新串起来,回答时要以 Vue2 的 defineProperty 为主,并能说明 Vue3 Proxy 解决了哪些边界。
真实面经题目 · 原创解析
这题考察 Vue 响应式如何把数据读写、依赖收集和视图更新串起来,回答时要以 Vue2 的 defineProperty 为主,并能说明 Vue3 Proxy 解决了哪些边界。
Vue 响应式的核心是把数据读取和数据修改变成可追踪的行为。Vue2 在初始化时递归遍历 data,用 Object.defineProperty 给属性加 getter/setter;组件渲染或 computed/watch 读取数据时,当前 watcher 会被 getter 收集到 dep 中;数据被 set 时,dep.notify 通知 watcher,watcher 再进入队列,异步批量更新视图。Vue2 对新增属性、删除属性、数组下标和 length 修改不够自然,所以需要 Vue.set、数组变异方法劫持等补丁。Vue3 用 Proxy 代理整个对象,能拦截 get、set、deleteProperty、has、ownKeys,也能更好支持 Map、Set 等集合类型,但 reactive/ref 的使用边界仍要分清。
响应式不是单纯监听变量变化,而是建立 data -> watcher -> render 的依赖关系。组件渲染读取了哪些字段,这些字段变化时才需要通知对应组件或计算属性更新。
Vue2 会对 data 做 observe,给每个对象属性通过 Object.defineProperty 定义 getter 和 setter。对象值会继续递归 observe,数组会替换原型上的 push、pop、splice 等变异方法来触发通知。
渲染 watcher、computed watcher 或用户 watcher 执行时会成为当前 active watcher。getter 被触发后,通过 dep.depend 把当前 watcher 收集起来,同一个字段可以对应多个 watcher,同一个 watcher 也可能依赖多个字段。
setter 比较新旧值后会重新 observe 新值,并通过 dep.notify 通知 watcher。组件 watcher 不会立刻同步重渲染,而是进入 scheduler 队列,去重后在 nextTick 中批量 flush,避免一次事件里多次 set 导致多次渲染。
defineProperty 只能劫持已有属性,所以新增属性和删除属性无法被自然追踪;数组通过索引赋值和直接改 length 也不会走变异方法。解决方式是 Vue.set、Vue.delete、splice,或者从一开始把响应式字段声明完整。
Proxy 代理对象本身,可以拦截属性读取、新增、删除、in、枚举等操作,集合类型也能做专门追踪。它让响应式覆盖面更完整,但解构 reactive 后丢失代理访问、ref 需要 .value、浅响应式和只读代理这些边界仍然需要说明。
因为 defineProperty 在初始化时只给已有 key 安装 getter/setter。后续直接 obj.newKey = value 只是普通赋值,没有 dep,也不会通知 watcher。
数组索引很多且 length 行为特殊,Vue2 没有逐个索引做 defineProperty,而是重写 push、pop、splice 等会改变数组的原型方法,在方法执行后通知更新。
computed 是惰性 watcher,依赖不变时读取缓存;watch 更偏副作用,当依赖变化后执行用户回调,适合异步请求、日志和桥接外部状态。
它让同一轮同步代码里的多次数据修改先被收集和去重,再统一刷新 DOM。nextTick 回调执行时通常能拿到本轮更新后的 DOM 状态。