Vue响应式原理基于“监听—记录—通知”机制:数据劫持(Object.defineProperty或Proxy)、依赖收集(渲染时自动建立属性与Watcher关联)、派发更新(setter触发notify→Watcher异步更新DOM)。

数据变更触发视图更新,靠的不是轮询或手动刷新,而是一套自动联动的“监听—记录—通知”机制。核心就三点:数据被劫持、依赖被记住、变化时广播。
数据劫持:让赋值操作变成可拦截的事件
Vue 在初始化 data 时,会把每个属性包装成带 getter/setter 的响应式单元。
- Vue 2 用 Object.defineProperty 拦截单个属性的读写;比如访问
this.message触发 getter,赋值this.message = 'hi'触发 setter。 - Vue 3 改用 Proxy,直接代理整个对象,能监听新增属性、删除、遍历等操作,能力更强也更自然。
- 注意:只有初始化时就在 data 里声明的属性才是响应式的;后续直接添加的属性(如
this.newField = 1)不会被劫持,需用Vue.set或this.$set补充。
依赖收集:渲染时悄悄记下“谁用了我”
组件首次渲染时,模板里读取数据的过程,就是依赖建立的过程。
- 例如
<div>{{ message }}</div>渲染时,会读取message→ 触发其 getter → 此时 Vue 把当前组件的 Watcher(渲染 watcher)存进message对应的依赖池(Dep)里。 - 这个过程是静默发生的,开发者无需干预;每个响应式属性都有自己的 Dep 实例,专门管理哪些 Watcher 关注它。
- 嵌套对象、数组元素同样会被递归劫持和收集——但要注意,Vue 2 无法检测数组索引直接赋值(
arr[0] = x)或 length 修改,得用splice、push等变异方法。
派发更新:setter 一动,所有相关视图立刻响应
当你修改响应式数据,setter 被触发,随即启动更新链路。
立即学习“前端免费学习笔记(深入)”;
- setter 内部调用
dep.notify(),遍历该属性所有已登记的 Watcher。 - 每个 Watcher 执行
update(),将组件标记为“待更新”,并推入异步队列(nextTick)批量处理,避免重复渲染。 - 最终触发重新执行 render 函数,生成新 VNode,再通过 diff 算法比对更新真实 DOM。
- 如果数据变更未被检测到(如给对象新增属性、深层对象直接改值),可用
this.$forceUpdate()强制刷新,或搭配triggerRef(针对 shallowRef)主动唤醒。
补充说明:为什么有些修改不生效?
常见失效场景都源于劫持边界:
-
对象新增属性:data 初始化后加的字段没被 defineProperty 劫持,要用
this.$set(obj, 'key', val)。 -
数组索引赋值或 length 修改:Vue 2 不监听这些操作,应改用
vm.items.$set(0, newValue)或splice。 -
shallowRef / shallowReactive 的深层属性:只代理第一层,改内部字段不会触发,需手动
triggerRef()或换用 reactive。


















