AI 生成的摘要
这篇文章深入探讨了 Vue.js 的响应式系统及其如何通过 JavaScript 的 Proxy 和 Reflect 对象实现数据追踪与更新。文章首先解释了响应式对象在 Vue 中的工作原理,强调 Vue 使用 JavaScript 的 Proxy 为对象提供代理以拦截基本操作,这样可以检测到对对象属性的访问和修改,并实时更新视图。文章接着详细说明了 Proxy 和 Reflect 的机制,以及它们如何协同工作以替代传统对象操作中的一些局限。特别是使用 Reflect,可以更方便地处理无法通过直接操作获取的信息。文章还深入讲解了 Vue 响应式系统中的关键概念,包括 targetMap、ReactiveEffect、Dep 等,以及它们如何配合,以支持 Vue 组件状态的自动更新。通过一系列代码示例,文章展示了如何搭建一个简化版本的 Vue 响应式系统,有助于读者理解底层机制。文章适合那些希望深入了解 Vue.js 响应式内部工作的开发者。

What is the Reactivity System?

Reactive objects are JavaScript proxies that behave like normal objects. The difference is that Vue can track property access and changes on reactive objects.

响应式对象是像普通对象一样行为的 JavaScript 代理。不同之处在于 Vue 可以跟踪响应式对象上的属性访问和变化。

One of Vue's most distinctive features is its modest Reactivity System. The state of a component is composed of reactive JavaScript objects. When the state changes, the view is updated.\ Vue 最显著的特点之一是其简洁的响应式系统。组件的状态由响应式 JavaScript 对象组成。当状态变化时,视图会更新。

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

link-faviconProxy是响应式的关键。

demo.ts
ts
const o = new Proxy(
  { value: 1, value2: 2 },

  {
    get(target, key, receiver) {
      console.log(`target:${target}, key: ${key}`)
      return target[key]
    },
    set(target, key, value, receiver) {
      console.log('hello from setter')
      target[key] = value
      return true
    },
  },
)

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 link-faviconproxy handler 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

link-faviconReflect 可以完成对对象的基本操作

什么是对象的基本操作?

目前所有的操作(调用、语法)都是间接的使用对象的基本操作。间接操作可能会导致一些其他问题。

  1. Object.keys()拿不到无法枚举的值,那么就可以使用Reflect.ownKeys()
  2. Reflect 可以 Reflect(target, key, otherTarget)

解决了什么问题?

ts
const obj = {
    a: 1,
    b: 2,
    get c() {
      return this.a + this.b
    }
}

// 如果直接 获取的话 那么 获取 target['c']时,get c 中的 this是 `obj`,而不是被 Proxy 代理的新对象。那么 get c 中 访问 this.a、this.b 时,不会触发 a、b 的 getter。 如果使用 Reflect 的话,直接 Reflect.get(target, key, receiver)即可。
ts
import { track, trigger } from './effect'
import { reactive } from './reactive'

export const mutableHandlers: ProxyHandler<object> = {
  get(target: object, key: string | symbol, receiver: object) {
    track(target, key)

    const res = Reflect.get(target, key, receiver)
    // reactive 只支持对象
    if (res !== null && typeof res === 'object') {
      return reactive(res)
    }

    return res
  },

  set(target: object, key: string | symbol, value: unknown, receiver: object) {
    let oldValue = (target as any)[key]
    Reflect.set(target, key, value, receiver)
    // 检查值是否已更改
    if (hasChanged(value, oldValue)) {
      trigger(target, key)
    }
    return true
  },
}

const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)

link-faviconreceiver

Vue 的响应式系统核心概念

Vue.js 的响应式系统涉及 targetProxyReflect, ReactiveEffectDeptracktriggertargetMap 和 activeEffect(目前是 activeSub)这几个概念

名称说明
target响应式目标对象
targetMap存储所有响应式目标及其依赖的全局 WeakMap
ReactiveEffect包装更新逻辑的类(如 updateComponent)
activeEffect当前正在执行的 ReactiveEffect
Dep存储一个属性对应的所有 ReactiveEffect
track依赖收集函数
trigger依赖触发函数
targetMap
ts
targetMap: WeakMap
  └─ target1: Map (KeyToDepMap)
      ├─ key1: Dep(Set of ReactiveEffect)
      └─ key2: Dep(...)
  └─ target2: ...
ts
type Target = any // 响应式目标
type TargetKey = any // 目标拥有的任何键

const targetMap = new WeakMap<Target, KeyToDepMap>() // 在此模块中定义为全局变量

type KeyToDepMap = Map<TargetKey, Dep> // 目标的键和效果的映射

type Dep = Set<ReactiveEffect> // dep 有多个 ReactiveEffects

class ReactiveEffect {
  constructor(
    // 这里,您给出想要实际应用为效果的函数(在这种情况下是 updateComponent)
    public fn: () => T,
  ) {}
}

也就是说 targetMap 中,keytarget, 每一个 target对应的是他们自己的依赖总Map,即 KeyToDepMap, KeyToDepMaptarget中每个key的依赖集合,集合中每一项都是ReactiveEffect

baseHandler.ts
ts
import { track, trigger } from './effect'
import { reactive } from './reactive'

export const mutableHandlers: ProxyHandler<object> = {
  get(target: object, key: string | symbol, receiver: object) {
    track(target, key)

    const res = Reflect.get(target, key, receiver)
    // reactive 只支持对象
    if (res !== null && typeof res === 'object') {
      return reactive(res)
    }

    return res
  },

  set(target: object, key: string | symbol, value: unknown, receiver: object) {
    let oldValue = (target as any)[key]
    Reflect.set(target, key, value, receiver)
    // 检查值是否已更改
    if (hasChanged(value, oldValue)) {
      trigger(target, key)
    }
    return true
  },
}

const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)