Vue响应式本质是数据劫持加依赖收集:Vue 2用Object.defineProperty拦截属性读写,Vue 3用Proxy代理整个对象;读取时收集Watcher/effect,修改时通知更新,通过异步队列(nextTick)批量派发。

Vue.js 的响应式不是“自动感知变化”,而是靠一套精密的拦截+追踪机制实现的。核心在于:数据一读就记下谁在用,数据一改就通知所有用过它的地方更新。
数据劫持:Vue 怎么“盯住”你的变量
Vue 2 用 Object.defineProperty 对 data 中每个属性做封装,在 getter 里记录依赖,在 setter 里触发更新。它只能劫持已存在的属性,新增属性或删除属性不会被监听,所以需要 Vue.set() 或 vm.$set() 来补救。
Vue 3 改用 Proxy,直接代理整个对象,能自然捕获属性增删、数组索引赋值、in 操作符等,不再有 Vue 2 的限制。一个 Proxy 实例就能处理嵌套对象和数组,无需递归遍历初始化。
- Vue 2 劫持是“深而早”:创建实例时就递归遍历所有属性
- Vue 3 劫持是“浅而懒”:首次访问嵌套属性时才代理,节省初始开销
- Proxy 无法 polyfill,所以 Vue 3 不支持 IE
依赖收集:谁用了这个数据,怎么记下来的
每次组件渲染时,会新建一个 Watcher(Vue 2)或 effect(Vue 3),并在执行模板取值过程中触发 getter / track。此时当前活跃的响应式副作用会被 Dep(Vue 2)或全局 targetEffect(Vue 3)捕获,并与该属性建立映射关系。
立即学习“前端免费学习笔记(深入)”;
比如 {{ count }} 渲染时读取 data.count,就会把当前组件的更新函数登记为 count 的订阅者。这个过程叫“依赖收集”,发生在视图首次求值阶段。
- 依赖关系是“细粒度”的:每个响应式字段单独维护订阅列表
- 计算属性(computed)也走同一套收集逻辑,但自带缓存和脏检查
- watch 本质也是 effect + 回调,只是不参与模板渲染流程
v-model 是怎么做到双向的
v-model 本身不是魔法,它只是语法糖。对普通 input 元素,v-model="msg" 等价于:
:value="msg" @input="msg = $event.target.value"
底层仍依赖响应式系统:value 绑定触发 getter(收集依赖),input 事件赋值触发 setter(派发更新)。自定义组件中,v-model 默认监听 modelValue prop 和 update:modelValue 事件,同样靠响应式驱动。
- 表单元素的原生事件(如 input、change)必须显式触发更新,否则视图不会变
- 非响应式对象上的属性绑定 v-model 不会生效——没有 setter 拦截
- 使用
shallowRef或markRaw会跳过响应式转换,v-model 失效
更新派发:从数据变到 DOM 刷新之间发生了什么
setter 触发后,不是立刻重绘 DOM,而是把相关 Watcher/effect 推入异步队列(microtask),由 nextTick 统一调度。这样可以合并多次数据变更,避免重复渲染。
Vue 3 进一步优化:同一个响应式对象的多个属性变更,只触发一次 effect 重新执行;不同对象的变更也尽量批处理,减少不必要的计算。
- DOM 更新是异步的,
nextTick提供回调时机,确保拿到最新 DOM - 响应式更新不等于重渲染:只有被模板实际引用的响应式字段变动,才会触发对应组件更新
- 冻结对象(
Object.freeze)或使用shallowRef可跳过响应式追踪,提升性能


















