vue源码讲解之 reactive解析(仅proxy部分)

在 Vue 3 中,reactive是一个核心的响应式 API,它的主要作用是将一个普通的JavaScript 对象或数组 转换为一个响应式代理对象。这意味着,当这个对象的属性值发生变化时,所有依赖该属性的地方(例如 Vue 模板、计算属性等)都会自动、同步地更新,从而实现数据与视图的绑定

1.2 用法

这里还没有到effect,就简单介绍一下 reactive 的代理功能

复制代码
import { reactive } from 'vue'
const original = { foo: 1 }
const observed = reactive(original)
function fun(val){
  console.log(val)
}
// get 访问
fun(observed.foo)
function isEqual(num1, num2){
  return num1 === num2
}
// set
observed.foo = 2
isEqual(observed.foo, original.foo) // true

1.3 源码解析

这里以用法中的代码为参照,讲一下代码关键部分和经过部分,其他的内容等待后续解锁(后面再具体去说)

当我们用法中代码执行的时候,首先会进入到reactive这个函数中,这里比较关键的部分在于 creatReactiveObjectmutableHandlers

位置:packages\reactivity\src\reactive.ts

复制代码
// 可响应对象映射
export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>() 
// 下面这个注释,是告诉打包工具这是一个无副作用函数
// 打包工具解析到这个注释可以进行更激进的"Tree shaking"
/*@__NO_SIDE_EFFECTS__*/
export function reactive(target: object) {
  
  // ....
  
  // 创建一个可响应的对象代理

  return createReactiveObject(
    target, // 目标对象
    false, // 不是只读对象
    mutableHandlers, // 可变代理处理函数
    mutableCollectionHandlers, // 可变集合代理处理函数
    reactiveMap, // 响应对象映射
  )
}

可以看到,这里使用了 createReactiveObject 创建了一个响应式代理对象

位置:packages\reactivity\src\reactive.ts

复制代码
enum TargetType {
  INVALID = 0, // 无效目标类型
  COMMON = 1, // 普通对象或数组
  COLLECTION = 2, // 集合对象
}

function createReactiveObject(
  target: Target, // 目标对象
  isReadonly: boolean, // 是否只读对象
  baseHandlers: ProxyHandler<any>, // 基础代理处理函数
  collectionHandlers: ProxyHandler<any>, // 集合代理处理函数
  proxyMap: WeakMap<Target, any>,// 弱引用映射,用于存储目标对象和对应的代理对象
) {
  // 非对象直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      warn(
        `value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
          target,
        )}`,
      )
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果目标是一个响应式对象,且不是只读对象,直接返回目标
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // only specific value types can be observed.
  // 只有特定的值类型才能被观察
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // target already has corresponding Proxy, return it.
  // 如果目标已经有一个对应的代理,直接返回代理
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 创建代理
  // 根据目标类型选择不同的代理处理函数
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  // 存储代理到弱引用映射中
  proxyMap.set(target, proxy)
  return proxy
}


// 目标类型映射函数
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON // 普通对象或数组
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION // 集合对象
    default:
      return TargetType.INVALID // 无效目标类型
  }
}

// 获取目标对象的类型
function getTargetType(value: Target) {
  // 跳过或者不可扩展的对象,返回无效类型,其他按照原始类型映射返回
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

位置:packages\reactivity\src\baseHandlers.ts

复制代码
export const mutableHandlers: ProxyHandler<object> =
  /*@__PURE__*/ new MutableReactiveHandler()
  
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    // 是否只只读对象
    protected readonly _isReadonly = false,
    // 是否为浅响应式对象
    protected readonly _isShallow = false,
  ) {}

  /**
   * 处理代理的 get 操作
   * @param target 目标对象
   * @param key 访问的属性名
   * @param receiver 目标对象的原型或接收者对象
   * @returns 访问结果
   */
  get(target: Target, key: string | symbol, receiver: object): any {

    // ...
    
    // 处理数组属性
    const targetIsArray = isArray(target)

    // ...

    // 映射目标对象的属性到代理对象

    const res = Reflect.get(
      target,
      key,
      receiver,
    )


    // ...
    
    // 判断访问的key是否是对象,如果是对象,将key的对应的对象变成响应式
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow = false) {
    super(false, isShallow)
  }
  // 处理代理的 set 操作
  set(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
  
    // ...
    
    const result = Reflect.set(
      target,
      key,
      value,
      isRef(target) ? target : receiver,
    )
    
    // ...


    return result
  }
  // 处理代理的 delete 操作
  deleteProperty(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
  ): boolean {
    const hadKey = hasOwn(target, key)
    const oldValue = target[key]
    const result = Reflect.deleteProperty(target, key)
    // ...
    return result
  }
  // 处理代理的 has 操作
  has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
    const result = Reflect.has(target, key)
    // ...
    return result
  }
  // 处理代理的 ownKeys 操作
  ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {

   // ...
   
    return Reflect.ownKeys(target)
  }
}

1.4 运行流程(调试流程)

1.4.1 编写调试代码,打断点

1.4.2 创建可响应式的对象

1.4.3 到达createReactiveObject函数内部,创建代理对象并返回

1.4.4 访问代理对象内部,触发get,映射目标对象的属性到代理对象,并返回

1.4.5 为代理对象赋值触发set,设置属性值

1.5 问题

1.5.1 为什么要使用Proxy 与 Reflect

一、 为什么选择 Proxy 作为核心代理机制?

  1. 全面的拦截能力与动态属性支持

**Proxy 可以拦截对一个对象的几乎所有基本操作,包括属性读取 (get)、设置 (set)、删除 (deleteProperty)、检查存在性 (has)、枚举 (ownKeys) 等。这意味着,无论是新增属性、删除属性,还是直接通过索引修改数组 (arr[0] = value),Proxy 都能自动感知并触发更新。**这彻底解决了 Vue 2 中必须依赖 Vue.set 和 Vue.delete 等特殊 API 来"打补丁"的尴尬局面,使得开发者可以像操作普通 JavaScript 对象一样自然地操作响应式数据,代码更加直观和简洁。

  1. 卓越的性能与按需响应

在 Vue 2 中,Object.defineProperty 必须在初始化时递归遍历对象的每一个已有属性并进行劫持,对于嵌套深、属性多的对象,初始化开销较大 。而 Proxy 是对整个对象进行一层代理,初始化时无需深度遍历,性能更优。更重要的是,Proxy 的 get 拦截是惰性的:只有在属性被实际访问时,才会进行依赖收集和可能的深层代理。这种"按需响应"的机制,使得 Vue 3 在处理大型对象和复杂嵌套结构时性能表现显著提升。

  1. 完美的数组响应式支持

Object.defineProperty 无法有效监听数组索引的直接赋值和 length 属性的变化。Vue 2 不得不通过重写数组的 7 个变异方法(如 push, pop)来曲线救国 ,但这仍无法覆盖所有场景。Proxy 则能原生拦截对数组的任何操作,包括通过索引修改、调用任何原生方法,甚至是修改 length,从而实现了真正完整、无需特殊处理的数组响应式。

二、 为什么必须配合 Reflect 使用?

尽管 Proxy 能力强大,但若单独使用,在复杂场景下会暴露出行为不一致和 this 指向错误等问题

  1. 确保正确的 this 上下文指向(核心原因)

这是 Reflect 在 Vue 3 响应式中最关键的作用。在 Proxy 的拦截器(如 get)内部,如果直接返回 target[key],那么当 target 的属性是一个 getter 访问器时,getter 函数内部的 this 将指向原始对象 target,而非代理对象 proxy。这会导致依赖收集错位,响应式链路断裂。**Reflect.get(target, key, receiver) 方法的第三个参数 receiver,可以显式指定操作中的 this 值。Vue 3 在调用时传入代理对象本身作为 receiver,从而确保无论访问器、继承链如何复杂,this 始终指向正确的响应式代理,**保证了依赖能够被准确收集到代理对象上,这是响应式系统正确工作的基石。

  1. 提供标准化、无副作用的默认行为

Reflect 的方法与 Proxy 的陷阱(trap)一一对应,其设计目的就是为 Proxy 提供一套规范化的默认行为实现 。**在 Vue 3 的拦截器中,通常会先调用 Reflect 的对应方法执行原始操作,然后再添加响应式逻辑(如 track 或 trigger)。**这样做有两大好处:

行为一致性:保证了代理对象在完成响应式增强后,其基本操作行为(如赋值、删除的成功与否)与原始对象保持一致,符合 JavaScript 的语言规范。

健壮性 :**Reflect 方法(如 Reflect.set)会返回一个布尔值来明确指示操作是否成功,而不是像直接赋值那样可能抛出异常。**这使得 Vue 3 可以更安全、更统一地处理操作结果,例如只在 set 成功后才触发更新 (trigger)。

  1. 函数式风格与未来兼容性

**Reflect 的 API 采用函数式风格,比对应的操作符或 Object 上的方法更统一、更易于组合和抽象。这提升了 Vue 3 源码的可读性和可维护性。**同时,作为 ES6 标准的一部分,Reflect 与 Proxy 的结合代表了 JavaScript 元编程的未来方向,为框架的长期演进奠定了更好的基础

1.6 总结

reactive是 Vue 3 的核心响应式 API,主要作用是将普通 JavaScript 对象或数组转换为响应式代理对象。当对象属性变化时,所有依赖该属性的地方都会自动更新,实现数据与视图的绑定。

相关推荐
乔磊1 小时前
我开发了一个 Ralph CLI
javascript
阿贵---1 小时前
单元测试在C++项目中的实践
开发语言·c++·算法
进击的尘埃1 小时前
Module Federation 2.0 共享策略翻车实录:版本协商、热更新与依赖冲突的排查工具链
javascript
2401_891482172 小时前
C++中的事件驱动编程
开发语言·c++·算法
码路飞2 小时前
不会 Rust 也能玩 WebAssembly:3 个 npm install 就能用的 WASM 神器
前端·javascript·webassembly
Sylus_sui2 小时前
鸿蒙Class实战:从零构建联系人列表
javascript
sudo_jin2 小时前
从“输入网址”到“帧级控制”:我对事件循环与主线程管理的终极认知
前端·javascript
sw1213892 小时前
C++与Rust交互编程
开发语言·c++·算法