响应式原理(Proxy)
约 973 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › 工程化与架构 › 响应式原理(Proxy)
Vue 3 响应式系统基于 Proxy 实现,比 Vue 2 的 Object.defineProperty 更强大——支持数组索引、属性增删。
核心机制
reactive 的实现
// 简化版 reactive
function reactive(target) {
if (target === null || typeof target !== 'object') return target
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
const result = Reflect.get(target, key, receiver)
// 深层响应式(延迟代理)
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
// 触发更新
trigger(target, key)
}
return result
},
deleteProperty(target, key) {
const hadKey = key in target
const result = Reflect.deleteProperty(target, key)
if (hadKey) {
trigger(target, key)
}
return result
}
})
}对比 Vue 2 的 defineProperty
// Vue 2 方式:递归遍历所有 key,逐一设置 getter/setter
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
// 依赖收集
dep.depend()
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
dep.notify()
}
}
})
}Vue 2 缺陷(defineProperty):
- 需要预先知道所有 key(遍历对象)
- 无法检测数组索引赋值(
arr[2] = 'x') - 无法检测属性新增(
obj.newKey = 1) - 需要
Vue.set()/Vue.delete()补救
Vue 3 优势(Proxy):
- 懒代理(访问到深层才代理,提升初始化性能)
- 支持数组索引、属性增删
- 无需
Vue.set()
依赖收集系统
targetMap 结构
// 全局依赖存储
const targetMap = new WeakMap() // 对象 → Map
let activeEffect = null // 当前正在执行的 effect
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect) // 当前 effect 添加到依赖集合
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => effect()) // 重新执行所有 effect
}
}ref 的实现
function ref(value) {
if (isRef(value)) return value
return new RefImpl(value)
}
class RefImpl {
constructor(value) {
this.__v_isRef = true
this._value = toReactive(value) // 对象 → reactive
}
get value() {
track(this, 'value')
return this._value
}
set value(newVal) {
if (!Object.is(newVal, this._value)) {
this._value = toReactive(newVal)
trigger(this, 'value')
}
}
}computed 的实现
class ComputedRefImpl {
constructor(getter) {
this._dirty = true
this._value = undefined
this.effect = new ReactiveEffect(getter, () => {
// scheduler:依赖变化时标记脏值
if (!this._dirty) {
this._dirty = true
trigger(this, 'value') // 触发依赖 computed 的 effect
}
})
}
get value() {
track(this, 'value')
if (this._dirty) {
this._dirty = false
this._value = this.effect.run() // 重新计算
}
return this._value
}
}关键设计:
_dirty标记:依赖未变化时不重新计算(惰性计算)- scheduler:依赖变化时设置
_dirty = true,但不立即重算 - 懒缓存:只有读
computed.value时,如果_dirty === true才计算
watchEffect 的实现
function watchEffect(effect) {
const doEffect = () => {
cleanup?.() // 清理上一个 effect 的副作用
activeEffect = doEffect // 设置当前 effect
cleanup = effect() // 执行 effect,触发 track
activeEffect = null
}
doEffect()
}完整响应式流程
影响性能的点
| 场景 | 优化方案 |
|---|---|
| 大数据对象不需要深层追踪 | 用 shallowRef |
| 短时间内多次修改 | Vue 会批量异步更新(nextTick) |
| 频繁读但不依赖渲染 | 用 rawValue 跳过 proxy |
| 大列表无需响应式 | 用 markRaw 标记为非响应式 |
import { markRaw } from 'vue'
const heavyData = markRaw(generateLargeArray())
// heavyData 不会被转为响应式,性能更好小结
| 概念 | 实现 |
|---|---|
| reactive | new Proxy(target, handler) + 延迟深层代理 |
| ref | 包装对象,get value / set value |
| 依赖收集 | WeakMap → Map → Set,effect 存储 |
| computed | 惰性计算 + _dirty 标记 + 独立 scheduler |
| Vue 2 缺陷 | 不支持数组索引、属性增删(需 Vue.set) |
上一节:Vite 高级配置与 Monorepo 下一节:虚拟 DOM 与编译优化
