Vue3响应式系统核心原理与实现详解
引言
Vue3的响应式系统是其性能提升和开发体验优化的核心所在。相比Vue2基于Object.defineProperty的实现,Vue3采用了更现代的Proxy API,带来了更全面的拦截能力和更好的性能表现。本文将深入解析Vue3响应式系统的实现原理,从核心概念到具体实现细节,帮助开发者深入理解这一重要机制。
核心原理概述
Vue3响应式系统的核心思想可以概括为四个步骤:
- 使用Proxy拦截对象的读取和设置操作
- 使用WeakMap存储副作用函数(依赖收集)
- 当读取属性时,收集依赖(track)
- 当设置属性时,触发更新(trigger)
为什么选择Proxy?
javascript
// Vue2使用Object.defineProperty
Object.defineProperty(obj, key, {
get() {
// 收集依赖
return value
},
set(newVal) {
// 触发更新
value = newVal
}
})
// Vue3使用Proxy
new Proxy(obj, {
get(target, key, receiver) {
// 收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
// 触发更新
return Reflect.set(target, key, value, receiver)
}
})
Proxy相比Object.defineProperty的优势:
- 可以拦截更多操作(delete、in、for...in等)
- 直接代理整个对象,无需递归遍历
- 性能更好,内存占用更少
核心数据结构
响应式系统的核心是依赖收集机制,Vue3使用多层嵌套的数据结构来管理依赖关系:
javascript
/**
* 存储副作用函数的"桶"
* 使用 WeakMap 结构:
* - key: 原始对象 (target)
* - value: Map 结构,存储该对象所有属性的副作用函数
*/
const bucket = new WeakMap()
/**
* 用于追踪 for...in 循环的 Symbol 键
* 当使用 for...in 遍历对象时,无法确定具体访问了哪个属性
* 因此使用一个特殊的 Symbol 键来存储遍历操作的副作用函数
*/
const ITERATE_KEY = Symbol()
/**
* 当前激活的副作用函数
* 用于在 track 函数中收集依赖
*/
let activeEffect
/**
* 副作用函数栈
* 用于处理嵌套的 effect 场景
*/
const effectStack = []
数据结构层次关系
text
bucket (WeakMap)
└─ target (原始对象) → depsMap (Map)
└─ key (属性名) → deps (Set)
└─ effectFn (副作用函数)
为什么使用WeakMap?
- WeakMap的键必须是对象,且是弱引用
- 当对象被垃圾回收时,WeakMap中的对应项也会自动清除
- 避免内存泄漏,不需要手动清理
创建响应式对象
Vue3提供了多种创建响应式对象的API,满足不同场景的需求:
reactive - 深度响应式
javascript
/**
* 创建深度响应式对象
* @param {Object|Array|Map|Set} obj - 要转换为响应式的原始对象
* @returns {Proxy} - 返回响应式代理对象
*/
function reactive(obj) {
// 检查缓存:如果已经创建过代理对象,直接返回
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 创建新的代理对象
const proxy = createReactive(obj)
// 存储到缓存中
reactiveMap.set(obj, proxy)
return proxy
}
createReactive - 核心创建函数
javascript
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
get(target, key, receiver) {
// 特殊属性处理
if (key === 'raw') return target
// 收集依赖
if (!isReadonly && typeof key !== 'symbol') {
track(target, key)
}
const res = Reflect.get(target, key, receiver)
// 浅响应直接返回
if (isShallow) return res
// 深度响应式处理
if (typeof res === 'object' && res !== null) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
},
set(target, key, newVal, receiver) {
// 只读检查
if (isReadonly) {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
return true
}
const oldVal = target[key]
const type = Array.isArray(target)
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
const res = Reflect.set(target, key, newVal, receiver)
// 触发更新条件判断
if (receiver.raw === target && oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
trigger(target, key, type, newVal)
}
return res
},
// 其他拦截器...
has(target, key) {
if (!isReadonly) track(target, key)
return Reflect.has(target, key)
},
ownKeys(target) {
if (!isReadonly) {
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
}
return Reflect.ownKeys(target)
},
deleteProperty(target, key) {
if (isReadonly) {
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`)
return true
}
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
trigger(target, key, 'DELETE')
}
return res
}
})
}
其他响应式API
javascript
// 浅响应式
function shallowReactive(obj) {
return createReactive(obj, true)
}
// 深度只读
function readonly(obj) {
return createReactive(obj, false, true)
}
// 浅只读
function shallowReadonly(obj) {
return createReactive(obj, true, true)
}
副作用系统
副作用系统是响应式机制的核心,负责建立数据与副作用函数之间的关联。
effect函数
javascript
/**
* 副作用函数
* 用于注册副作用函数,当依赖的响应式数据改变时,自动重新执行
*/
function effect(fn, options = {}) {
const effectFn = () => {
// 清理旧依赖
cleanup(effectFn)
// 设置当前激活的副作用函数
activeEffect = effectFn
effectStack.push(effectFn)
// 执行副作用函数
const res = fn()
// 恢复之前的状态
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
return res
}
effectFn.options = options
effectFn.deps = []
if (!options.lazy) {
effectFn()
}
return effectFn
}
依赖收集与触发
javascript
/**
* 收集依赖(追踪)
* 当访问响应式数据的属性时,建立属性与副作用函数的联系
*/
function track(target, key) {
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 建立双向联系
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
/**
* 触发更新
* 当修改响应式数据的属性时,执行所有依赖于这个属性的副作用函数
*/
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
const effectsToRun = new Set()
effects && effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
// 特殊处理:数组操作
if (type === 'ADD' && Array.isArray(target)) {
const lengthEffects = depsMap.get('length')
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 特殊处理:数组length修改
if (Array.isArray(target) && key === 'length') {
depsMap.forEach((effects, key) => {
const numericKey = Number(key)
if (!isNaN(numericKey) && numericKey >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 特殊处理:遍历操作
if (type === 'ADD' || type === 'DELETE' ||
(type === 'SET' && (target instanceof Map || target instanceof Set))) {
const iterateEffects = depsMap.get(ITERATE_KEY)
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 执行副作用函数
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
依赖清理
javascript
/**
* 清理副作用函数的依赖
* 处理分支切换的情况(如 if 条件)
*/
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
计算属性
计算属性是基于响应式数据的衍生值,具有缓存机制。
javascript
/**
* 计算属性
* 基于响应式数据计算出一个值,具有缓存机制
*/
function computed(getter) {
let value
let dirty = true
let effectFn
const obj = {
get value() {
if (dirty) {
value = effectFn()
dirty = false
}
// 收集依赖
track(obj, 'value')
return value
}
}
effectFn = effect(getter, {
lazy: true,
scheduler() {
if (!dirty) {
dirty = true
// 触发更新
trigger(obj, 'value')
}
}
})
return obj
}
计算属性的特点:
- 懒计算:只有在访问value时才计算
- 缓存机制:依赖数据未改变时直接返回缓存值
- 响应式:依赖改变时标记为dirty,下次访问重新计算
监听器
监听器用于观察响应式数据的变化,执行相应的回调函数。
javascript
/**
* 监听器
* 监听响应式数据的变化,当数据改变时执行回调函数
*/
function watch(source, cb, options = {}) {
let getter
if (typeof source === "function") {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue, newValue
let cleanup
function onInvalidate(fn) {
cleanup = fn
}
const job = () => {
newValue = effectFn()
if (cleanup) {
cleanup()
}
cb(oldValue, newValue, onInvalidate)
oldValue = newValue
}
const effectFn = effect(() => getter(), {
lazy: true,
scheduler: () => {
if (options.flush === 'post') {
const p = Promise.resolve()
p.then(job)
} else {
job()
}
}
})
if (options.immediate) {
job()
} else {
oldValue = effectFn()
}
}
/**
* 遍历对象
* 递归遍历对象的所有属性,用于建立响应式联系
*/
function traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) return
seen.add(value)
for (const k in value) {
traverse(value[k], seen)
}
return value
}
Ref API
Ref用于创建原始值的响应式引用。
javascript
/**
* 创建原始值的响应式引用
*/
function ref(val) {
const wrapper = {
value: val
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return reactive(wrapper)
}
/**
* 将响应式对象的某个属性转为 ref
*/
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key]
},
set value(val) {
obj[key] = val
}
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
/**
* 将响应式对象的所有属性转为 ref
*/
function toRefs(obj) {
const ret = {}
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
/**
* 自动脱 ref
* 在模板中访问 ref 时自动获取 .value 的值
*/
function proxyRefs(target) {
return new Proxy(target, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
return value.__v_isRef ? value.value : value
},
set(target, key, newValue, receiver) {
const value = target[key]
if (value.__v_isRef) {
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
特殊类型处理
数组方法重写
javascript
const arrayInstrumentations = {}
// 重写数组的查找方法
['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function (...args) {
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
res = originMethod.apply(this.raw, args)
}
return res
}
})
// 重写会修改数组长度的方法
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function (...args) {
shouldTrack = false
let res = originMethod.apply(this, args)
shouldTrack = true
return res
}
})
Map/Set方法重写
javascript
const mutableInstrumentations = {
add(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.add(key)
if (!hadKey) {
trigger(target, key, 'ADD')
}
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
},
get(key) {
const target = this.raw
const had = target.has(key)
track(target, key)
if (had) {
const res = target.get(key)
return typeof res === 'object' && res !== null ? reactive(res) : res
}
},
set(key, value) {
const target = this.raw
const had = target.has(key)
const oldValue = target.get(key)
const rawValue = value && value.raw ? value.raw : value
target.set(key, rawValue)
if (!had) {
trigger(target, key, 'ADD')
} else if (oldValue !== value || (oldValue === oldValue && value === value)) {
trigger(target, key, 'SET')
}
return this
},
forEach(callback, thisArg) {
const wrap = (val) => typeof val === 'object' && val !== null ? reactive(val) : val
const target = this.raw
track(target, ITERATE_KEY)
target.forEach((v, k) => {
callback.call(thisArg, wrap(v), wrap(k), this)
})
}
}
性能优化策略
Vue3响应式系统包含多种性能优化策略:
- 懒执行:computed属性只有在被访问时才计算
- 依赖清理:副作用函数重新执行前清理旧依赖
- 调度器:控制副作用函数的执行时机
- 分支切换优化:处理条件分支导致的依赖变化
- 缓存机制:避免重复计算和重复代理
总结
Vue3的响应式系统通过Proxy实现了更全面、更高效的响应式机制。其核心在于:
- 依赖收集:通过track函数建立数据与副作用函数的关联
- 触发更新:通过trigger函数在数据变化时执行相关副作用
- 精细控制:通过调度器控制副作用执行时机
- 全面覆盖:支持对象、数组、Map、Set等多种数据类型
- 性能优化:通过多种策略确保系统高效运行
理解Vue3响应式系统的实现原理,不仅有助于更好地使用Vue3,也能为开发者设计和实现自己的响应式系统提供宝贵参考。这种基于Proxy的响应式实现方式,代表了现代前端框架的发展方向,值得深入学习和掌握。