Vue3 的响应式系统源码级 readonly 与 shallowReadonly 的实现原理与区别
字数 627 2025-11-18 22:39:48
Vue3 的响应式系统源码级 readonly 与 shallowReadonly 的实现原理与区别
题目描述
今天讲解 Vue3 响应式系统中 readonly 和 shallowReadonly 这两个 API 的实现原理。它们都用于创建只读的响应式对象,但在"只读深度"上存在关键差异。需要深入源码分析其代理逻辑、访问拦截和嵌套处理机制。
核心概念解析
- readonly:创建深度只读代理对象,所有层级的属性都不可修改
- shallowReadonly:创建浅层只读代理对象,仅第一层属性只读,深层属性仍可修改
- 两者都基于 Proxy 实现,但在嵌套属性处理上采用不同策略
实现原理逐步解析
第一步:基础只读处理器创建
// 基础只读处理器,禁止所有写入操作
const readonlyHandlers = {
get: createGetter(true), // 只读模式标识
set(target, key) {
// 严格模式下抛出错误,开发环境警告
if (__DEV__) {
console.warn(`Set operation on key "${key}" failed: target is readonly.`)
}
return true
},
deleteProperty(target, key) {
// 禁止删除操作
if (__DEV__) {
console.warn(`Delete operation on key "${key}" failed: target is readonly.`)
}
return true
}
}
第二步:getter 函数的差异化实现
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 响应式标识访问
if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
const res = Reflect.get(target, key, receiver)
// 浅层只读:直接返回原始值,不进行深度代理
if (shallow) {
return res
}
// 深度只读:对嵌套对象进行递归代理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
第三步:readonly 的完整实现
function readonly(target) {
return createReactiveObject(
target,
true, // isReadonly = true
readonlyHandlers,
readonlyCollectionHandlers // 集合类型的特殊处理
)
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
// 校验目标类型,避免重复代理
if (!isObject(target)) {
return target
}
// 使用 WeakMap 缓存代理对象
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建 Proxy 实例
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
第四步:shallowReadonly 的特殊处理
// 浅层只读处理器,继承自只读处理器但重写 getter
const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
get: createGetter(true, true) // isReadonly=true, shallow=true
})
function shallowReadonly(target) {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers
)
}
第五步:实际使用场景对比
const original = {
count: 1,
nested: { value: 2 }
}
const deepReadonlyObj = readonly(original)
const shallowReadonlyObj = shallowReadonly(original)
// 深度只读:所有层级都不可修改
deepReadonlyObj.count = 100 // 警告,修改失败
deepReadonlyObj.nested.value = 200 // 警告,修改失败
// 浅层只读:仅第一层只读,嵌套对象可修改
shallowReadonlyObj.count = 100 // 警告,修改失败
shallowReadonlyObj.nested.value = 200 // 修改成功!嵌套对象未被代理
设计思想总结
- 通过组合模式实现代码复用:shallowReadonly 基于 readonly 扩展
- 标志位传递:通过 isReadonly 和 shallow 参数控制代理深度
- 性能优化:避免不必要的深度代理,shallowReadonly 在需要只读表层时更高效
- 类型安全:在编译时和运行时都提供相应的类型检查和警告
这种分层设计使 Vue3 的响应式系统既能保证数据不可变性(readonly),又能根据性能需求灵活控制代理深度(shallowReadonly)。