Skip to content

响应式系统(浅析)

核心模块

Reactive API: 创建响应式对象(如 reactive、ref、computed)

依赖搜集系统:追踪属性访问与副作用函数的依赖关系(track trigger)

副作用函数:包裹响应式操作的函数(effect)

javascript
/**
 * @description: reactive 函数   reactive响应的起点函数
 * @param {object} target
 * @returns {*}
 */
export function reactive(target: object) {
  console.log("初始位置");
  debugger;
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果目标对象是只读的,则直接返回目标对象
  if (isReadonly(target)) {
    return target;
  }
  // 创建响应式对象
  return createReactiveObject(
    target,
    false,
    mutableHandlers, // 创建响应式对象的处理器
    mutableCollectionHandlers, // 创建响应式对象的集合处理器
    reactiveMap // 代理对象
  );
}
javascript
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  /**
   * 如果target不是对象,则直接返回target
   */
  if (!isObject(target)) {
    return target;
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  /**
   * 如果target是proxy,则直接返回target
   */
  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已经存在对应的Proxy,则直接返回
   */
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  // collectionHandlers
  // baseHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  );
  // 将代理对象设置到代理对象映射表中
  proxyMap.set(target, proxy);
  // 返回代理对象
  return proxy;
}
javascript
/**
 * 可变响应式处理器,继承自 BaseReactiveHandler
 * 用于处理可变的响应式对象,支持属性的设置、删除等操作
 */
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow = false) {
    super(false, isShallow);
    console.log("MutableReactiveHandler", isShallow);
  }

  /**
   * Proxy 的 set 函数,当设置响应式对象的属性时会被调用
   * 这是响应式系统中触发更新的核心方法
   * @param target 原始目标对象
   * @param key 要设置的属性键
   * @param value 要设置的新值
   * @param receiver Proxy 对象本身
   * @returns 是否设置成功
   */
  set(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    debugger;
    // 获取目标对象上该属性的旧值,用于后续的比较和触发更新。
    let oldValue = target[key];

    // 非浅响应式模式下的特殊处理
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue);
      // value 不是浅响应式,且不是只读的
      if (!isShallow(value) && !isReadonly(value)) {
        //  value 和 oldValue 转换为原始对象
        oldValue = toRaw(oldValue);
        value = toRaw(value);
      }

      // 处理 ref 的特殊情况:如果旧值是 ref 而新值不是 ref
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false;
        } else {
          // 直接修改 ref 的 value,而不是替换整个 ref
          oldValue.value = value;
          return true;
        }
      }
    } else {
      // 在浅模式下,对象按原样设置,无论是否为响应式
    }

    // 判断目标对象上是否存在该属性
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length // 数组的整数键:检查索引是否在长度范围内
        : hasOwn(target, key); // 普通对象:检查属性是否存在

    // 使用 Reflect.set 实际设置属性值
    const result = Reflect.set(
      target,
      key,
      value,
      isRef(target) ? target : receiver
    );

    // 只有当 target 是 receiver 的原始对象时才触发更新
    // 这避免了在原型链上的对象触发不必要的更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 新增属性:触发 ADD 操作
        trigger(target, TriggerOpTypes.ADD, key, value);
      } else if (hasChanged(value, oldValue)) {
        // 修改属性且值发生变化:触发 SET 操作
        trigger(target, TriggerOpTypes.SET, key, value, oldValue);
      }
    }
    // 返回设置结果
    return result;
  }
}
javascript
// 基类,所有响应式处理器的基类 new Proxy(target, handler) 中的 handler
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false, // 是否为只读模式
    protected readonly _isShallow = false, // 是否为浅响应式模式
  ) {}

  /**
   * Proxy 的 get 陷阱函数,当访问响应式对象的属性时会被调用
   * @param target 原始目标对象
   * @param key 要访问的属性键
   * @param receiver Proxy 对象本身
   * @returns 属性值
   */
  get(target: Target, key: string | symbol, receiver: object): any {
    // 处理特殊标志位,用于跳过响应式处理
    if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]

    const isReadonly = this._isReadonly,
      isShallow = this._isShallow

    // 处理响应式标志位查询
    if (key === ReactiveFlags.IS_REACTIVE) {
      // 查询对象是否为响应式:只读对象不算响应式
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      // 查询对象是否为只读
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      // 查询对象是否为浅响应式
      return isShallow
    } else if (key === ReactiveFlags.RAW) {
      // 获取原始对象,绕过代理
      if (
        receiver ===
          (isReadonly
            ? isShallow
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver 不是响应式代理,但有相同的原型
        // 这意味着 receiver 是响应式代理的用户代理
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // 提前返回 undefined
      return
    }

    // 判断目标对象是否为数组
    const targetIsArray = isArray(target)

    // 非只读模式下处理特殊逻辑
    if (!isReadonly) {
      let fn: Function | undefined
      // 如果是数组且存在数组方法重写,返回重写的方法
      if (targetIsArray && (fn = arrayInstrumentations[key])) {
        return fn
      }
      // 处理 hasOwnProperty 方法,确保依赖收集
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    // 使用 Reflect.get 获取属性值    核心
    const res = Reflect.get(
      target,
      key,
      // 如果这是一个包装 ref 的代理,使用原始 ref 作为 receiver
      // 这样我们就不需要在所有类方法中调用 `toRaw` 了
      isRef(target) ? target : receiver,
    )

    // 对于内置 Symbol 和非可追踪的键,直接返回结果,不进行依赖收集
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 非只读模式下进行依赖收集(响应式系统的核心)
    if (!isReadonly) {
      // 依赖收集  核心代码
      track(target, TrackOpTypes.GET, key)
    }

    // 浅响应式模式直接返回结果,不进行深层转换
    if (isShallow) {
      return res
    }

    // 处理 ref 的解包
    if (isRef(res)) {
      // ref 解包 - 对于数组 + 整数键跳过解包
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    // 如果返回值是对象,将其转换为响应式对象
    if (isObject(res)) {
      // 将返回值也转换为代理。我们在这里进行 isObject 检查
      // 以避免无效值警告。还需要延迟访问 readonly
      // 和 reactive 以避免循环依赖。
      return isReadonly ? readonly(res) : reactive(res)
    }

    // 返回原始值
    return res
  }
}

依赖搜集系统(track trigger)

track

javascript
/**
 * 追踪对响应式属性的访问
 *
 * 这将检查当前正在运行的effect,并将其记录为dep
 * dep记录所有依赖于该响应式属性的effects
 *
 * @param target - 持有响应式属性的对象
 * @param type - 定义对响应式属性的访问类型
 * @param key - 要追踪的响应式属性的标识符
 */
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  // shouldTrack 表示是否应该进行依赖收集
  // activeSub 表示当前正在执行的副作用函数(effect)
  if (shouldTrack && activeSub) {
    // 获取目标对象的依赖映射
    let depsMap = targetMap.get(target);
    // 如果targetMap中没有target对应的depsMap,则创建一个
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }

    // 获取属性的依赖集合
    let dep = depsMap.get(key);
    // 如果depsMap中没有key对应的dep,则创建一个
    if (!dep) {
      depsMap.set(key, (dep = new Dep()));
      dep.map = depsMap;
      dep.key = key;
    }

    // 调用dep的track方法进行依赖收集
    if (__DEV__) {
      dep.track({
        target,
        type,
        key,
      });
    } else {
      dep.track();
    }
  }
}
javascript
// dep.track()
/**
   * 追踪依赖的核心方法
   *
   * 参数说明:
   * - activeSub:当前正在执行的副作用函数(effect)
   * - shouldTrack:是否应该进行依赖收集
   * - activeSub === this.computed:避免计算属性自己收集自己作为依赖
   */
  track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
    // 条件检查:如果没有活跃的effect,或者不应该追踪,或者当前effect是computed,则返回
    if (!activeSub || !shouldTrack || activeSub === this.computed) {
      return
    }

    // 获取或创建Link连接
    // this.activeLink 表示当前活跃的effect与该dep之间的链接
    let link = this.activeLink
    // 如果link不存在,或者link的sub不等于activeSub,则为当前activeSub创建一个新的Link连接
    if (link === undefined || link.sub !== activeSub) {
      // 创建新的Link连接
      link = this.activeLink = new Link(activeSub, this)

      /**
       * 建立副作用函数到依赖的双向链表:
       * 每个副作用函数维护一个deps链表,记录它依赖的所有响应式数据
       * 新创建的link被添加到链表尾部
       */
      if (!activeSub.deps) {
        // 如果当前执行的副作用函数没有deps链表,则将link作为deps的头部 & 尾部
        activeSub.deps = activeSub.depsTail = link
      } else {
        // 将link添加到deps链表尾部
        link.prevDep = activeSub.depsTail
        activeSub.depsTail!.nextDep = link
        activeSub.depsTail = link
      }

      // 调用addSub函数,将link添加到当前dep的订阅者列表中
      addSub(link)  // ****************
    } else if (link.version === -1) {
      // 重用上次运行的链接 - 已经是订阅者,只需同步版本
      link.version = this.version

      /**
       * 如果这个dep有next,说明它不在尾部 - 将其移动到尾部
       * 这确保了effect的dep列表按照在求值期间访问的顺序排列
       */
      if (link.nextDep) {
        const next = link.nextDep
        next.prevDep = link.prevDep
        if (link.prevDep) {
          link.prevDep.nextDep = next
        }

        // 将link移动到尾部
        link.prevDep = activeSub.depsTail
        link.nextDep = undefined
        activeSub.depsTail!.nextDep = link
        activeSub.depsTail = link

        // 如果这是头部,指向新的头部
        if (activeSub.deps === link) {
          activeSub.deps = next
        }
      }
    }
    return link
  }
javascript
/**
 * 添加订阅者的核心函数
 * 建立dep到effect的双向链表
 */
function addSub(link: Link) {
  // 增加订阅者计数器
  link.dep.sc++;

  // 检查是否正在追踪
  if (link.sub.flags & EffectFlags.TRACKING) {
    // 处理计算属性的特殊情况
    const computed = link.dep.computed;
    // 计算属性第一次获得订阅者时,启用追踪并订阅其所有依赖
    if (computed && !link.dep.subs) {
      computed.flags |= EffectFlags.TRACKING | EffectFlags.DIRTY;
      for (let l = computed.deps; l; l = l.nextDep) {
        addSub(l);
      }
    }

    // 建立dep到effect的双向链表
    const currentTail = link.dep.subs;
    if (currentTail !== link) {
      link.prevSub = currentTail;
      if (currentTail) currentTail.nextSub = link;
    }

    // 开发环境下设置subsHead
    if (__DEV__ && link.dep.subsHead === undefined) {
      link.dep.subsHead = link;
    }

    // 更新dep的订阅者列表
    link.dep.subs = link;
  }
}

trigger

javascript
/**
 * 查找与目标(或特定属性)关联的所有deps,并触发其中存储的effects
 *
 * @param target - 响应式对象
 * @param type - 定义需要触发effects的操作类型
 * @param key - 可用于定位目标对象中的特定响应式属性
 * @param newValue - 新值
 * @param oldValue - 旧值
 * @param oldTarget - 旧的目标对象(用于Map/Set)
 */
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
): void {
  debugger;
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    // 从未被追踪过
    globalVersion++;
    return;
  }

  /**
   * 运行dep的触发器函数
   */
  const run = (dep: Dep | undefined) => {
    if (dep) {
      if (__DEV__) {
        dep.trigger({
          target,
          type,
          key,
          newValue,
          oldValue,
          oldTarget,
        });
      } else {
        dep.trigger();
      }
    }
  };

  startBatch();

  if (type === TriggerOpTypes.CLEAR) {
    // 集合被清空,触发目标的所有effects
    depsMap.forEach(run);
  } else {
    const targetIsArray = isArray(target);
    const isArrayIndex = targetIsArray && isIntegerKey(key);

    if (targetIsArray && key === "length") {
      // 数组长度变化时的特殊处理
      const newLength = Number(newValue);
      depsMap.forEach((dep, key) => {
        if (
          key === "length" ||
          key === ARRAY_ITERATE_KEY ||
          (!isSymbol(key) && key >= newLength)
        ) {
          run(dep);
        }
      });
    } else {
      // 为SET | ADD | DELETE操作调度运行
      if (key !== void 0 || depsMap.has(void 0)) {
        run(depsMap.get(key));
      }

      // 为任何数字键变化调度ARRAY_ITERATE(length在上面处理)
      if (isArrayIndex) {
        run(depsMap.get(ARRAY_ITERATE_KEY));
      }

      // 也为ADD | DELETE | Map.SET的迭代键运行
      switch (type) {
        case TriggerOpTypes.ADD:
          if (!targetIsArray) {
            // 对象添加属性
            run(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          } else if (isArrayIndex) {
            // 数组添加新索引 -> 长度变化
            run(depsMap.get("length"));
          }
          break;
        case TriggerOpTypes.DELETE:
          if (!targetIsArray) {
            // 对象删除属性
            run(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          }
          break;
        case TriggerOpTypes.SET:
          if (isMap(target)) {
            // Map设置值
            run(depsMap.get(ITERATE_KEY));
          }
          break;
      }
    }
  }

  endBatch();
}
javascript
/**
   * 触发更新:增加版本号并通知所有订阅者
   */
  trigger(debugInfo?: DebuggerEventExtraInfo): void {
    this.version++ // 增加dep的版本号
    globalVersion++ // 增加全局版本号
    this.notify(debugInfo) // 通知所有订阅者
  }

  /**
   * 通知所有订阅者执行更新
   */
  notify(debugInfo?: DebuggerEventExtraInfo): void {
    startBatch() // 开始批处理
    try {
      if (__DEV__) {
        // 在开发环境下,按原始顺序调用onTrigger钩子
        for (let head = this.subsHead; head; head = head.nextSub) {
          if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
            head.sub.onTrigger(
              extend(
                {
                  effect: head.sub,
                },
                debugInfo,
              ),
            )
          }
        }
      }

      // 遍历所有订阅者,触发更新
      for (let link = this.subs; link; link = link.prevSub) {
        if (link.sub.notify()) {
          // 如果notify()返回true,说明这是一个computed
          // 同时调用其dep的notify,在这里调用而不是在computed的notify内部调用
          // 是为了减少调用栈深度
          ;(link.sub as ComputedRefImpl).dep.notify()
        }
      }
    } finally {
      endBatch() // 结束批处理
    }
  }
javascript
/**
 * 结束批处理,执行所有批处理的副作用
 * @internal
 */
export function endBatch(): void {
  if (--batchDepth > 0) {
    return
  }

  // 先处理计算属性
  if (batchedComputed) {
    let e: Subscriber | undefined = batchedComputed
    batchedComputed = undefined
    while (e) {
      const next: Subscriber | undefined = e.next
      e.next = undefined
      e.flags &= ~EffectFlags.NOTIFIED
      e = next
    }
  }

  // 处理普通副作用
  let error: unknown
  while (batchedSub) {
    let e: Subscriber | undefined = batchedSub
    batchedSub = undefined
    while (e) {
      const next: Subscriber | undefined = e.next
      e.next = undefined
      e.flags &= ~EffectFlags.NOTIFIED
      if (e.flags & EffectFlags.ACTIVE) {
        try {
          // ACTIVE 标志仅用于副作用
          ;(e as ReactiveEffect).trigger() // ******
        } catch (err) {
          if (!error) error = err
        }
      }
      e = next
    }
  }
}


/**
   * 触发副作用
   * 根据状态决定如何执行
   */
  trigger(): void {
    if (this.flags & EffectFlags.PAUSED) {
      // 如果暂停,加入暂停队列
      pausedQueueEffects.add(this)
    } else if (this.scheduler) {
      // 如果有自定义调度器,使用调度器
      this.scheduler()
    } else {
      // 否则直接运行
      this.runIfDirty()  // this.run()
    }
  }
typescript
/**
   * 运行副作用函数
   * 这是副作用执行的核心方法
   */
  run(): T {
    // TODO cleanupEffect

    // 如果副作用已停止,直接执行函数
    if (!(this.flags & EffectFlags.ACTIVE)) {
      // stopped during cleanup
      return this.fn()
    }

    // 设置运行标志
    this.flags |= EffectFlags.RUNNING
    // 清理之前的副作用
    cleanupEffect(this)
    // 准备依赖追踪
    prepareDeps(this)

    // 保存当前活跃副作用
    const prevEffect = activeSub
    const prevShouldTrack = shouldTrack
    activeSub = this
    shouldTrack = true

    try {
      // 执行副作用函数
      return this.fn() //指向被包裹的函数
    } finally {
      // 开发环境检查
      if (__DEV__ && activeSub !== this) {
        warn(
          'Active effect was not restored correctly - ' +
            'this is likely a Vue internal bug.',
        )
      }
      // 清理依赖
      cleanupDeps(this)
      // 恢复之前的活跃副作用
      activeSub = prevEffect
      shouldTrack = prevShouldTrack
      // 清除运行标志
      this.flags &= ~EffectFlags.RUNNING
    }
  }

副作用函数(effect)

  • Vue 3 的 watch 和 watchEffect 内部都使用 effect
  • 组件的渲染函数会被包装成 effect
  • 计算属性 computed 也基于 effect 实现
  • 用户可以直接使用 effect 来创建自定义的响应式副作用
typescript
/**
 * 创建副作用函数
 *
 * Vue 3 的 watch 和 watchEffect 内部都使用 effect
 * 组件的渲染函数会被包装成 effect
 * 计算属性 computed 也基于 effect 实现
 * 用户可以直接使用 effect 来创建自定义的响应式副作用
 */
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner<T> {
  // 如果传入的已经是运行器,提取其副作用函数
  if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
    fn = (fn as ReactiveEffectRunner).effect.fn;
    // fn就是最后执行的副作用
  }

  // 创建副作用函数
  const e = new ReactiveEffect(fn);
  if (options) {
    extend(e, options);
  }
  try {
    e.run();
  } catch (err) {
    e.stop();
    throw err;
  }
  const runner = e.run.bind(e) as ReactiveEffectRunner;
  runner.effect = e;
  return runner;
}

通过测试文件断点调试

javascript
<script src="../dist/vue.global.js"></script>

<div id="demo">
    <h3>{{count.value}}</h3>
    <button @click="addFn">add+1</button>
    <button @click="reduceFn">reduce-1</button>
</div>

<script>
    const { createApp, ref, reactive, watchEffect } = Vue

    debugger
    let app = createApp({

        setup() {
            const count = reactive({
                value: 0
            })
            const addFn = () => {
                debugger
                count.value++
            }
            const reduceFn = () => {
                count.value--
            }
            return {
                count,
                addFn,
                reduceFn
            }
        },
    })
    debugger
    app.mount('#demo')
</script>

<style>
</style>

断点执行流程(部分)

初始化阶段

点击更新数据阶段

触发 addFn 函数

触发 BaseReactiveHandler 中的 get 方法 (暂时先关注执行的代码)

Reflect.get 的第三个参数决定了属性访问器 get 函数的 this 指向

javascript
// 基类,所有响应式处理器的基类 new Proxy(target, handler) 中的 handler
// receiver  发起属性访问的对象  通常是proxy本身
get(target: Target, key: string | symbol, receiver: object): any {
  // 是不是只读
  const isReadonly = this._isReadonly,
      isShallow = this._isShallow

  // Reflect.get 读取target的key属性
  const res = Reflect.get(
    target,
    key,
    // if this is a proxy wrapping a ref, return methods using the raw ref
    // as receiver so that we don't have to call `toRaw` on the ref in all
    // its class methods
    isRef(target) ? target : receiver,
    // 若target已经是ref代理对象了,则指向target  否则this指向receiver
  )
  // 非只读
  if (!isReadonly) {
    // 依赖收集  核心代码
    track(target, TrackOpTypes.GET, key) // ***************
  }

  return res
}
问题 1: 为什么要用 Reflect.get
  1. this

在访问对象的 getter 属性原型链上的属性 时,this 指向会影响依赖收集的正确性。

举个例子:

javascript
const obj = {
  name: "Vue",
  get fullName() {
    // getter 中访问了 this.name
    return this.name + " 3";
  },
};

const proxy = new Proxy(obj, {
  get(target, property, receiver) {
    // 依赖收集:记录当前副作用函数与 property 的关联
    track(target, property);
    // 错误写法:直接返回 target[property]
    return target[property];
  },
});

当访问 proxy.fullName 时:

  • target[property] 会执行 obj.fullName getter,此时 getter 中的 this 指向 原始对象** ****obj\*\*(而非代理 proxy)。
  • 因此,getter 中访问的 this.name 会直接读取 obj.name不会经过** **proxy** **的** **get** **拦截器,导致 name 属性的依赖无法被收集。
  • 后续若修改 proxy.namefullName 依赖的 name 变化不会触发更新,响应式失效。
`Reflect.get``this`

Reflect.get(target, property, receiver) 的第三个参数 receiver 可以指定 getter 执行时的 this 指向。

在 Vue 3 的响应式中,receiver 就是当前的代理对象(proxy)。修改上面的代码:

javascript
const proxy = new Proxy(obj, {
  get(target, property, receiver) {
    track(target, property);
    // 正确写法:用 Reflect.get 并传入 receiver
    return Reflect.get(target, property, receiver);
  },
});

此时访问 proxy.fullName

  • Reflect.get 会让 getter 中的 this 指向 receiver(即 proxy)。
  • 因此,getter 中访问的 this.name 会触发 proxy.nameget 拦截,进而被 track 收集依赖。
  • 后续修改 proxy.name 时,fullName 的依赖会被正确触发更新。

如果对象的属性来自 原型链,直接用 target[property] 会跳过代理的拦截逻辑,导致依赖收集遗漏。

举个例子:

javascript
// 原型对象
const proto = {
  age: 18,
};
// 原始对象,继承自 proto
const obj = Object.create(proto);
// 代理 obj
const proxy = new Proxy(obj, {
  get(target, property, receiver) {
    track(target, property);
    return target[property]; // 错误写法
  },
});

当访问 proxy.age 时:

  • obj 本身没有 age 属性,target[property] 会直接去原型链 proto 上找 age,但 proto 没有被代理,因此 age 的访问 不会经过** **proxy** **的** **get** **拦截,导致 age 的依赖无法收集。
`Reflect.get`

Reflect.get 会严格按照 对象的原型链查找规则 执行,且在查找过程中若遇到代理对象,会触发其 get 拦截器。

修改代码后:

javascript
get(target, property, receiver) {
  track(target, property);
  return Reflect.get(target, property, receiver);
}

此时访问 proxy.age

  • Reflect.get 会先查 obj(无 age),再查原型 proto。但由于 receiverproxy,原型链上的属性访问也会通过 proxy 的拦截器,确保依赖被正确收集。
`Proxy`

当代理对象被 继承 时,receiver 可能是继承了代理的子对象,而非代理本身。Reflect.get 能保证这种场景下的 this 指向正确。

例如:

javascript
const proxy = new Proxy(
  { name: "Vue" },
  {
    get(target, property, receiver) {
      track(target, property);
      return Reflect.get(target, property, receiver);
    },
  }
);

// 子对象继承自 proxy
const child = Object.create(proxy);
console.log(child.name); // 访问子对象的 name 属性

此时 child.name 会触发 proxyget 拦截,receiverchildReflect.get 会正确处理继承关系,确保 name 的依赖收集到 proxy 上,后续修改 proxy.name 时,child 的依赖也会更新。

问题 2: receiver 的作用
保持 getter 的 this 绑定

如果直接返回 target[property],当属性是 getter 时,getter 内部的 this 会指向原始对象 target,而非代理对象 proxy。这可能导致:

  • 无法触发代理的其他拦截器(如 set)。
  • 在响应式系统(如 Vue 3)中失效。

正确做法:使用 Reflect.get(target, property, receiver) 转发属性访问,确保 this 指向 proxy

处理继承场景

当代理对象被继承时,receiver 会是继承自 proxy 的对象,而非 proxy 本身

触发 track 方法,但是并未执行内部逻辑,activeSub 是 undefined

javascript
/**
 * Tracks access to a reactive property.
 *
 * This will check which effect is running at the moment and record it as dep
 * which records all effects that depend on the reactive property.
 *
 * 将检查当前正在运行中的effect,并记录为dep,dep记录所有依赖于该响应式属性的effect
 *
 * @param target - Object holding the reactive property. 响应式对象
 * @param type - Defines the type of access to the reactive property. 访问类型
 * @param key - Identifier of the reactive property to track. 响应式属性
 */

// target >  depsMap  >  dep
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  if (shouldTrack && activeSub) {
    // fasle
    /***/
  }
}

触发 MutableReactiveHandler 中的 set 方法

javascript
set(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    // 获取目标对象上该属性的旧值,用于后续的比较和触发更新。
    let oldValue = target[key]
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue)
      // value 不是浅响应式,且不是只读的
      if (!isShallow(value) && !isReadonly(value)) {
        //  value 和 oldValue 转换为原始对象
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
    }

    // 判断目标对象上是否存在该属性
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // set设置新值
    const result = Reflect.set(
      target,
      key,
      value,
      isRef(target) ? target : receiver,
    )
    // 触发更新   ===========
   if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }  // ***

    // 返回设置结果   true  false
    return result
  }

触发 trigger 方法

javascript
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
): void {
  debugger;
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    // never been tracked
    globalVersion++;
    return;
  }
  const run = (dep: Dep | undefined) => {
    if (dep) {
      if (__DEV__) {
        dep.trigger({
          target,
          type,
          key,
          newValue,
          oldValue,
          oldTarget,
        });
      } else {
        dep.trigger(); /*****trigger*****/
      }
    }
  };

  startBatch();

  // schedule runs for SET | ADD | DELETE
  if (key !== void 0 || depsMap.has(void 0)) {
    run(depsMap.get(key)); //  执行run
  }

  endBatch(); //  进入任务调度
}

made with ❤️ by ankang