03-reactive与Proxy代理机制
本章目标:深入理解reactive系列API的实现原理,掌握Proxy拦截的完整机制
上一章我们建立了响应式系统的全局视角,知道了Vue通过Proxy实现数据劫持。但"知道"和"理解"之间还有很长的距离。当你写下reactive(obj)的那一刻,Vue内部究竟发生了什么?这一章,我们将逐行拆解reactive的实现,看看数据劫持这门"艺术"是如何完成的。
2.1 从reactive入口开始
reactive()函数的源码出人意料地简洁:
// 文件: packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// 如果目标已经是readonly代理,直接返回
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
真正的逻辑都在createReactiveObject这个工厂函数里。它需要处理多种边界情况,让我们用一张流程图来理解:
flowchart TD
A["reactive(target)"] --> B{是否为对象?}
B -->|否| C["⚠️ 开发环境警告<br/>返回原值"]
B -->|是| D{已是Proxy?}
D -->|是| E{是readonly调用reactive?}
E -->|是| F["继续处理"]
E -->|否| G["返回已有Proxy"]
D -->|否| H{目标类型检查}
H -->|INVALID| C
H -->|COMMON<br/>Object/Array| I["使用baseHandlers"]
H -->|COLLECTION<br/>Map/Set等| J["使用collectionHandlers"]
I --> K{缓存中存在?}
J --> K
K -->|是| L["返回缓存的Proxy"]
K -->|否| M["new Proxy(target, handlers)"]
M --> N["存入proxyMap缓存"]
N --> O["返回Proxy"]
这个流程体现了几个重要的设计决策:
1. 非对象直接返回
Proxy只能代理对象,原始值(string、number等)无法被代理。这也是为什么Vue需要ref来处理原始值。
2. 避免重复代理
通过检查ReactiveFlags.RAW标记判断目标是否已经是Proxy。如果是,直接返回,避免创建"代理的代理"。
3. 目标类型分流
Vue将目标对象分为三类:
COMMON:普通对象和数组,使用baseHandlersCOLLECTION:Map、Set、WeakMap、WeakSet,使用collectionHandlersINVALID:不可扩展的对象或标记了__v_skip的对象,不进行代理
4. 缓存策略
使用WeakMap存储target到Proxy的映射,确保同一个对象只会被代理一次:
// 文件: packages/reactivity/src/reactive.ts
export const reactiveMap: WeakMap<Target, any> = new WeakMap()
export const shallowReactiveMap: WeakMap<Target, any> = new WeakMap()
export const readonlyMap: WeakMap<Target, any> = new WeakMap()
export const shallowReadonlyMap: WeakMap<Target, any> = new WeakMap()
四种响应式变体各自维护独立的缓存,互不干扰。WeakMap的弱引用特性还允许原对象被垃圾回收时,相关的Proxy也能被自动清理。
来看createReactiveObject的核心实现:
// 文件: packages/reactivity/src/reactive.ts (简化)
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// 1. 非对象直接返回
if (!isObject(target)) {
if (__DEV__) warn(`value cannot be made reactive: ${String(target)}`)
return target
}
// 2. 已是Proxy则返回(除非是对reactive对象调用readonly)
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 3. 检查目标类型
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 4. 检查缓存
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 5. 创建Proxy并缓存
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
2.2 Proxy Handler的分层设计
Proxy的核心在于handler——它定义了如何拦截对象的各种操作。Vue的handler设计采用了优雅的类继承结构:
classDiagram
class BaseReactiveHandler {
#_isReadonly: boolean
#_isShallow: boolean
+get(target, key, receiver)
}
class MutableReactiveHandler {
+set(target, key, value, receiver)
+deleteProperty(target, key)
+has(target, key)
+ownKeys(target)
}
class ReadonlyReactiveHandler {
+set() "⚠️ 警告并返回true"
+deleteProperty() "⚠️ 警告并返回true"
}
BaseReactiveHandler <|-- MutableReactiveHandler
BaseReactiveHandler <|-- ReadonlyReactiveHandler
这种设计的好处是:get trap的逻辑在所有变体中是共享的,而set/deleteProperty的行为则根据是否readonly而不同。
get trap:读取拦截的艺术
get trap是整个响应式系统的入口,它承担了多重职责:
// 文件: packages/reactivity/src/baseHandlers.ts (简化)
class BaseReactiveHandler {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target, key, receiver) {
const isReadonly = this._isReadonly
const isShallow = this._isShallow
// 1. 处理特殊标记访问
if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
if (key === ReactiveFlags.IS_READONLY) return isReadonly
if (key === ReactiveFlags.IS_SHALLOW) return isShallow
if (key === ReactiveFlags.RAW) {
// 验证receiver是否是正确的代理
if (receiver === proxyMap.get(target)) return target
return
}
const targetIsArray = isArray(target)
// 2. 数组方法特殊处理
if (!isReadonly && targetIsArray) {
const fn = arrayInstrumentations[key]
if (fn) return fn
}
// 3. hasOwnProperty特殊处理
if (key === 'hasOwnProperty') return hasOwnProperty
// 4. 获取属性值
const res = Reflect.get(target, key, receiver)
// 5. 跳过Symbol和不可追踪的key
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 6. 依赖收集(readonly不收集)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 7. 浅层响应式直接返回
if (isShallow) return res
// 8. ref自动解包
if (isRef(res)) {
return targetIsArray && isIntegerKey(key) ? res : res.value
}
// 9. 嵌套对象懒代理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
让我们逐一理解这些职责:
特殊标记访问:当访问__v_isReactive、__v_raw等特殊属性时,返回相应的标记值。这些标记不是真正存储在对象上的,而是通过get trap"虚拟"出来的。
数组方法特殊处理:数组的某些方法需要特殊处理,我们稍后详细讨论。
依赖收集:这是响应式的核心——调用track()记录当前正在执行的副作用函数依赖了这个属性。
嵌套对象懒代理:这是Vue 3的一个重要优化。不同于Vue 2在初始化时递归遍历整个对象,Vue 3只在访问嵌套属性时才创建Proxy。这意味着如果你有一个深层嵌套的对象,但只访问了第一层属性,那么深层的对象永远不会被代理。
set trap:写入拦截与更新触发
// 文件: packages/reactivity/src/baseHandlers.ts (简化)
class MutableReactiveHandler extends BaseReactiveHandler {
set(target, key, value, receiver) {
let oldValue = target[key]
// 1. 深度响应式模式下的处理
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// ref自动解包赋值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) return true
oldValue.value = value
return true
}
}
// 2. 判断是新增还是修改
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 3. 执行实际的赋值
const result = Reflect.set(target, key, value, receiver)
// 4. 触发更新(只有当target是原始对象时才触发)
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
set trap的关键点:
区分ADD和SET:新增属性触发TriggerOpTypes.ADD,修改属性触发TriggerOpTypes.SET。这个区分很重要,因为某些依赖(如for...in循环)只关心属性的增删,不关心值的变化。
ref自动解包赋值:如果旧值是ref,新值不是ref,会自动更新ref的value而不是替换整个ref。
原型链保护:target === toRaw(receiver)这个检查确保只有直接操作目标对象时才触发更新,避免原型链上的操作导致重复触发。
其他trap
// 文件: packages/reactivity/src/baseHandlers.ts (简化)
class MutableReactiveHandler extends BaseReactiveHandler {
// 删除属性
deleteProperty(target, key) {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
// in操作符
has(target, key) {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
// Object.keys/for...in等
ownKeys(target) {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
}
2.3 深度响应式与浅层响应式
Vue提供了四种响应式API,它们的区别在于"深度"和"可变性"两个维度:
| API | 深度代理 | 可变性 | ref解包 | 典型场景 |
|---|---|---|---|---|
reactive | ✅ 懒代理 | 可变 | ✅ | 一般状态对象 |
shallowReactive | ❌ 仅第一层 | 可变 | ❌ | 大对象、外部数据 |
readonly | ✅ 懒代理 | 只读 | ✅ | 不可变数据、props |
shallowReadonly | ❌ 仅第一层 | 只读 | ❌ | 组件props |
"懒代理"是Vue 3的一个重要优化。来看这个例子:
const state = reactive({
user: {
profile: {
name: 'Vue',
settings: {
theme: 'dark'
}
}
}
})
// 此时只有state是Proxy
// user、profile、settings都还是普通对象
console.log(state.user.profile.name)
// 访问时才递归创建Proxy:
// state.user -> Proxy
// state.user.profile -> Proxy
// 但settings仍然是普通对象,因为没有访问它
这种设计的好处是:
- 初始化快:不需要递归遍历整个对象
- 内存省:未访问的深层对象不会创建Proxy
- 按需代理:只有真正用到的数据才会被代理
shallowReactive则完全不进行深度代理:
const state = shallowReactive({
nested: { count: 0 }
})
state.nested = { count: 1 } // ✅ 触发响应
state.nested.count++ // ❌ 不触发响应,nested不是响应式的
2.4 集合类型的特殊处理
Map、Set、WeakMap、WeakSet这些集合类型的响应式实现与普通对象完全不同。原因在于:Proxy的trap无法直接拦截集合的方法调用。
const map = new Map()
const proxy = new Proxy(map, {
get(target, key) {
console.log('get:', key)
return target[key] // 这样会报错!
}
})
proxy.set('a', 1) // TypeError: Method Map.prototype.set called on incompatible receiver
问题在于Map的方法内部会检查this是否是真正的Map实例。当通过Proxy调用时,this是Proxy而不是Map,导致报错。
Vue的解决方案是"方法劫持"——在get trap中返回包装后的方法。这套方案的核心在于createInstrumentations函数,它为集合的每个方法创建响应式包装:
// 文件: packages/reactivity/src/collectionHandlers.ts (简化)
function createInstrumentations(readonly, shallow) {
const instrumentations = {
// Map.get() 的响应式包装
get(key) {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
// 收集依赖:同时追踪原始key和响应式key
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.GET, key)
}
track(rawTarget, TrackOpTypes.GET, rawKey)
}
// 获取值并转换为响应式
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
const { has } = getProto(rawTarget)
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
}
},
// Map.set() / Set.add() 的响应式包装
set(key, value) {
if (!shallow && !isShallow(value) && !isReadonly(value)) {
value = toRaw(value)
}
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
}
const oldValue = get.call(target, key)
target.set(key, value)
// 触发更新:区分ADD和SET
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return this
},
// size属性的响应式处理
get size() {
const target = this[ReactiveFlags.RAW]
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
return target.size
},
// has() 方法
has(key) {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (!readonly) {
if (hasChanged(key, rawKey)) {
track(rawTarget, TrackOpTypes.HAS, key)
}
track(rawTarget, TrackOpTypes.HAS, rawKey)
}
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
},
// forEach() 方法
forEach(callback, thisArg) {
const observed = this
const target = observed[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
!readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
return target.forEach((value, key) => {
// 确保回调接收到响应式值
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
},
// delete(), clear() 等方法...
}
return instrumentations
}
关键技巧是:通过this[ReactiveFlags.RAW]获取原始的Map/Set对象,然后在原始对象上调用方法,同时手动进行依赖收集和更新触发。
迭代器方法的包装
集合的迭代器方法(keys、values、entries、Symbol.iterator)需要特殊处理,确保迭代出的值也是响应式的:
// 文件: packages/reactivity/src/collectionHandlers.ts (简化)
function createIterableMethod(method, isReadonly, isShallow) {
return function(...args) {
const target = this[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const targetIsMap = isMap(rawTarget)
const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
const innerIterator = target[method](...args)
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 追踪迭代依赖
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY)
// 返回包装后的迭代器
return {
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done,
}
},
[Symbol.iterator]() {
return this
},
}
}
}
集合handler的简洁设计
与普通对象需要多个trap不同,集合的handler只需要一个get trap:
// 文件: packages/reactivity/src/collectionHandlers.ts (简化)
function createInstrumentationGetter(isReadonly, shallow) {
const instrumentations = createInstrumentations(isReadonly, shallow)
return (target, key, receiver) => {
// 处理特殊标记
if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
if (key === ReactiveFlags.IS_READONLY) return isReadonly
if (key === ReactiveFlags.RAW) return target
// 如果是被劫持的方法,返回包装后的版本
return Reflect.get(
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver,
)
}
}
export const mutableCollectionHandlers = {
get: createInstrumentationGetter(false, false),
}
export const readonlyCollectionHandlers = {
get: createInstrumentationGetter(true, false),
}
这种设计的巧妙之处在于:所有的响应式逻辑都封装在instrumentations对象中,handler本身非常简洁。当访问集合的方法时,返回的是包装后的方法;当访问其他属性时,直接返回原始值。
2.5 数组响应式的完整实现
数组虽然是对象,但它有一些特殊的行为需要处理。Vue在arrayInstrumentations中重写了数组的大部分方法,这是一个相当复杂的模块。
数组方法的分类处理
Vue将数组方法分为几类,每类有不同的处理策略:
flowchart TB
subgraph "搜索方法"
A["includes / indexOf / lastIndexOf"]
A --> A1["双重查找:先用原参数,再用toRaw后的参数"]
end
subgraph "变异方法"
B["push / pop / shift / unshift / splice"]
B --> B1["暂停追踪 + 批处理"]
end
subgraph "迭代方法"
C["map / filter / forEach / every / some / find"]
C --> C1["包装回调,确保item是响应式的"]
end
subgraph "迭代器方法"
D["values / entries / Symbol.iterator"]
D --> D1["包装迭代器,确保yield的值是响应式的"]
end
搜索方法的双重查找
indexOf、includes、lastIndexOf这些搜索方法需要特殊处理:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
function searchProxy(self, method, args) {
const arr = toRaw(self)
track(arr, TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY)
// 先用原始参数搜索
const res = arr[method](...args)
// 如果没找到,且参数是Proxy,用原始值再搜索一次
if ((res === -1 || res === false) && isProxy(args[0])) {
args[0] = toRaw(args[0])
return arr[method](...args)
}
return res
}
为什么需要双重查找?考虑这个场景:
const obj = {}
const state = reactive([obj])
state.includes(obj) // 应该返回true
state.includes(reactive(obj)) // 也应该返回true
用户可能用原始对象搜索,也可能用响应式对象搜索,两种情况都应该能找到。
变异方法的追踪暂停
push、pop、shift、unshift、splice这些方法会修改数组长度,如果不特殊处理会导致无限循环:
const arr = reactive([])
watchEffect(() => {
arr.push(1) // 读取length -> 收集依赖 -> 修改length -> 触发更新 -> 重新执行 -> 无限循环!
})
Vue的解决方案是在这些方法执行期间暂停依赖追踪:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
function noTracking(self, method, args = []) {
pauseTracking() // 暂停追踪
startBatch() // 开始批处理
const res = toRaw(self)[method].apply(self, args)
endBatch() // 结束批处理
resetTracking() // 恢复追踪
return res
}
// push、pop等方法都使用noTracking包装
const arrayInstrumentations = {
push(...args) { return noTracking(this, 'push', args) },
pop() { return noTracking(this, 'pop') },
shift() { return noTracking(this, 'shift') },
unshift(...args) { return noTracking(this, 'unshift', args) },
splice(...args) { return noTracking(this, 'splice', args) },
}
迭代方法的响应式包装
map、filter、forEach等迭代方法需要确保回调函数接收到的是响应式值:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
function apply(self, method, fn, thisArg, wrappedRetFn, args) {
const arr = shallowReadArray(self) // 追踪ARRAY_ITERATE依赖
const needsWrap = arr !== self && !isShallow(self)
// 包装回调,确保item是响应式的
let wrappedFn = fn
if (arr !== self) {
if (needsWrap) {
wrappedFn = function(item, index) {
return fn.call(this, toReactive(item), index, self)
}
} else if (fn.length > 2) {
// 如果回调使用了第三个参数(array),确保传入的是代理数组
wrappedFn = function(item, index) {
return fn.call(this, item, index, self)
}
}
}
const result = arr[method](wrappedFn, thisArg)
return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result
}
这确保了以下代码能正常工作:
const state = reactive([{ count: 0 }, { count: 1 }])
state.forEach(item => {
item.count++ // item是响应式的,修改会触发更新
})
const doubled = state.map(item => item.count * 2) // item.count的访问会被追踪
迭代器方法的包装
values()、entries()、Symbol.iterator等迭代器方法需要确保yield的值是响应式的:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
function iterator(self, method, wrapValue) {
const arr = shallowReadArray(self) // 追踪ARRAY_ITERATE依赖
const iter = arr[method]()
if (arr !== self && !isShallow(self)) {
// 包装迭代器的next方法
iter._next = iter.next
iter.next = () => {
const result = iter._next()
if (!result.done) {
result.value = wrapValue(result.value)
}
return result
}
}
return iter
}
// 使用示例
const arrayInstrumentations = {
[Symbol.iterator]() {
return iterator(this, Symbol.iterator, item => toReactive(item))
},
values() {
return iterator(this, 'values', item => toReactive(item))
},
entries() {
return iterator(this, 'entries', ([index, item]) => [index, toReactive(item)])
},
}
reduce/reduceRight的特殊处理
reduce方法需要特殊处理,因为它的回调签名与其他迭代方法不同:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
function reduce(self, method, fn, args) {
const arr = shallowReadArray(self)
let wrappedFn = fn
if (arr !== self) {
if (!isShallow(self)) {
// 包装回调,确保item是响应式的
wrappedFn = function(acc, item, index) {
return fn.call(this, acc, toReactive(item), index, self)
}
} else if (fn.length > 3) {
// 如果回调使用了第四个参数(array)
wrappedFn = function(acc, item, index) {
return fn.call(this, acc, item, index, self)
}
}
}
return arr[method](wrappedFn, ...args)
}
不可变数组方法
ES2023引入的不可变数组方法(toReversed、toSorted、toSpliced)也得到了支持:
// 文件: packages/reactivity/src/arrayInstrumentations.ts
const arrayInstrumentations = {
toReversed() {
return reactiveReadArray(this).toReversed()
},
toSorted(comparer) {
return reactiveReadArray(this).toSorted(comparer)
},
toSpliced(...args) {
return reactiveReadArray(this).toSpliced(...args)
},
}
这些方法返回新数组,不修改原数组,所以不需要暂停追踪。
2.6 工具函数:toRaw、markRaw、isReactive
Vue还提供了一些工具函数来操作响应式对象:
toRaw:获取响应式对象的原始对象
// 文件: packages/reactivity/src/reactive.ts
export function toRaw<T>(observed: T): T {
const raw = observed && observed[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed // 递归处理嵌套的Proxy
}
使用场景:需要临时读取数据而不触发依赖收集,或者需要将数据传递给不支持Proxy的第三方库。
markRaw:标记对象永不转换为响应式
// 文件: packages/reactivity/src/reactive.ts
export function markRaw<T extends object>(value: T): Raw<T> {
if (!hasOwn(value, ReactiveFlags.SKIP) && Object.isExtensible(value)) {
def(value, ReactiveFlags.SKIP, true)
}
return value
}
使用场景:某些对象不需要响应式(如第三方类实例、大型不可变数据),标记后可以避免不必要的代理开销。
isReactive / isReadonly / isProxy:类型检查
// 文件: packages/reactivity/src/reactive.ts
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive(value[ReactiveFlags.RAW])
}
return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
export function isReadonly(value: unknown): boolean {
return !!(value && value[ReactiveFlags.IS_READONLY])
}
export function isProxy(value: any): boolean {
return value ? !!value[ReactiveFlags.RAW] : false
}
本章涉及源码:
packages/reactivity/src/reactive.ts- reactive/readonly/shallowReactive/markRaw/toRawpackages/reactivity/src/baseHandlers.ts- Object/Array的Proxy handlerspackages/reactivity/src/collectionHandlers.ts- Map/Set的Proxy handlerspackages/reactivity/src/arrayInstrumentations.ts- 数组方法增强
Proxy拦截只是响应式的第一步,真正的魔法在于拦截时如何收集依赖、变化时如何触发更新。你可能已经注意到代码中频繁出现的track()和trigger()函数——它们是整个响应式系统的心脏。下一章,我们将深入这两个函数的实现,揭开依赖追踪的神秘面纱。
🤔 深度思考
-
缓存一致性:Vue使用WeakMap缓存Proxy,如果同一个对象被多次传入reactive会发生什么?如果先调用reactive再调用readonly呢?这种设计如何保证响应式的一致性?
-
无限循环:为什么数组的push方法需要暂停依赖追踪?请尝试推演如果不暂停会发生什么——从watchEffect执行开始,一步步追踪依赖收集和触发更新的过程。
-
性能权衡:shallowReactive和markRaw都可以避免深度代理,它们的使用场景有什么区别?在性能优化时应该如何选择?
-
设计局限:集合类型(Map/Set)的响应式实现采用"方法劫持"而非直接Proxy,这种方式有什么局限性?能否想到其他可能的实现方案?