Vue3 的响应式系统源码级 readonly 与 shallowReadonly 的实现原理与区别
字数 627 2025-11-18 22:39:48

Vue3 的响应式系统源码级 readonly 与 shallowReadonly 的实现原理与区别

题目描述
今天讲解 Vue3 响应式系统中 readonly 和 shallowReadonly 这两个 API 的实现原理。它们都用于创建只读的响应式对象,但在"只读深度"上存在关键差异。需要深入源码分析其代理逻辑、访问拦截和嵌套处理机制。

核心概念解析

  1. readonly:创建深度只读代理对象,所有层级的属性都不可修改
  2. shallowReadonly:创建浅层只读代理对象,仅第一层属性只读,深层属性仍可修改
  3. 两者都基于 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 // 修改成功!嵌套对象未被代理

设计思想总结

  1. 通过组合模式实现代码复用:shallowReadonly 基于 readonly 扩展
  2. 标志位传递:通过 isReadonly 和 shallow 参数控制代理深度
  3. 性能优化:避免不必要的深度代理,shallowReadonly 在需要只读表层时更高效
  4. 类型安全:在编译时和运行时都提供相应的类型检查和警告

这种分层设计使 Vue3 的响应式系统既能保证数据不可变性(readonly),又能根据性能需求灵活控制代理深度(shallowReadonly)。

Vue3 的响应式系统源码级 readonly 与 shallowReadonly 的实现原理与区别 题目描述 今天讲解 Vue3 响应式系统中 readonly 和 shallowReadonly 这两个 API 的实现原理。它们都用于创建只读的响应式对象,但在"只读深度"上存在关键差异。需要深入源码分析其代理逻辑、访问拦截和嵌套处理机制。 核心概念解析 readonly:创建深度只读代理对象,所有层级的属性都不可修改 shallowReadonly:创建浅层只读代理对象,仅第一层属性只读,深层属性仍可修改 两者都基于 Proxy 实现,但在嵌套属性处理上采用不同策略 实现原理逐步解析 第一步:基础只读处理器创建 第二步:getter 函数的差异化实现 第三步:readonly 的完整实现 第四步:shallowReadonly 的特殊处理 第五步:实际使用场景对比 设计思想总结 通过组合模式实现代码复用:shallowReadonly 基于 readonly 扩展 标志位传递:通过 isReadonly 和 shallow 参数控制代理深度 性能优化:避免不必要的深度代理,shallowReadonly 在需要只读表层时更高效 类型安全:在编译时和运行时都提供相应的类型检查和警告 这种分层设计使 Vue3 的响应式系统既能保证数据不可变性(readonly),又能根据性能需求灵活控制代理深度(shallowReadonly)。