案例
通过以下这个案例来进行理解:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../../dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { reactive, effect } = Vue
      
      const obj = reactive({
        name: 'first',
        age: 18
      })
      
      effect(() => {
        document.querySelector('#app').innerHTML = obj.name
      })
      setTimeout(() => {
        obj.name = 'last'
      }, 2000)
    </script>
  </body>
</html>
        首先引入 reactive 和 effect 两个函数,创建了一个响应式对象obj,并使用 effect 注册了一个副作用函数,该函数将 obj.name 的值渲染到页面上,2 秒后,我们修改了 obj.name 的值,页面也随之更新
createReactiveObject方法
reactive函数在packages/reactivity/src/reactive.ts文件下,它的主要作用是将普通对象转换为响应式对象

reactive函数实际执行的是 createReactiveObject 方法,该方法也在该文件下
- target:我们需要传入的对象
 - isReadonly:要创建的代理是否为只可读
 - baseHandlers 是对基本类型的劫持,即 [Object, Array]
 - collectionHandlers 是对集合类型的劫持,即 [Set, Map, WeakMap, WeakSet]
 - proxyMap:WeakMap 类型,用于缓存已经创建的代理对象
 
如果 target 不是对象类型,直接返回原值,只有对象类型的数据才能被响应式系统处理;如果 target 已经是 Proxy 类型,并且 isReadonly 为 false,直接返回目标对象本身,因为 Proxy 对象已经被处理过,无需重复处理

该函数实际做了 proxyMap 缓存处理,最终返回一个 proxy 实例对象 。这里主要关注new Proxy这段代码,第一个参数target为传进来的对象,即{ name: 'first', age: 18 },第二个baseHandlers参数即传入的 mutableHandlers 对象 ,该对象定义在packages/reactivity/src/baseHandlers.ts中:

该对象定义了 get、set 等方法,从而对传入的数据进行依赖收集和依赖触发 ,先看下结果后面再对这部分逻辑进行分析,reactive函数执行完毕,obj 得到了一个 proxy 的实例对象 ,接下来执行effect方法

effect方法
该方法定义在packages/reactivity/src/effect.ts文件中,它用于创建副作用函数,能够自动追踪其内部依赖的响应式数据变化,并在数据变更时重新执行 ,它是watch、computed等 API 的底层实现基础
(可以这样理解:副作用函数就像是一个"观察者",它观察着某些数据的变化,一旦数据变化,它就会采取行动)
fn 是需要被追踪的副作用函数,若 fn 已被 effect 包装过,则提取其原始函数。声明一个构造函数ReactiveEffect的实例对象_effect,执行构造函数中的run方法

ReactiveEffect构造函数如下:
active:表示该副作用是否活跃,默认为truedeps:一个数组,用于存储该副作用函数所依赖的依赖项parent:表示该副作用的父副作用,默认为undefined
run()执行流程:若active为false,直接执行fn但不收集依赖。设置activeEffect = this,标记当前活跃的副作用,运行 fn() ,触发响应式数据的 get 拦截器,完成依赖收集
stop():标记active = false,若当前正在执行自身(activeEffect === this),标记deferStop,待执行结束后清理;调用cleanupEffect(this)从所有依赖集合中移除当前副作用
export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined
  // 省略
  
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }
  run() {
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true
      // 省略
      // 执行 fn 函数 即传入的匿名函数
      //  () => {
      //     document.querySelector('#app').innerHTML = obj.name
      //  }
      return this.fn()
    } finally {
      // 省略
      if (this.deferStop) {
        this.stop()
      }
    }
  }
  stop() {
    // stopped while running itself - defer the cleanup
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}
        这里接收一个 fn 方法即传入的匿名函数,然后设置 active、deps、parent 等属性:

之后执行_effect.run(),即执行构造函数ReactiveEffect的run方法,需要关注activeEffect = this,此时被赋值为:

然后执行fn函数,即执行传入的匿名函数,之后执行document.querySelector('#app').innerHTML = obj.name触发obj的get方法
get/set
get方法上述中被定义在packages/reactivity/src/baseHandlers.ts文件中,实际触发的是createGetter函数,主要关注**track(target, TrackOpTypes.GET, key)** 这段代码,它是对数据的依赖收集, 也是get方法的核心
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 省略
    const targetIsArray = isArray(target)
    // 省略
    // Reflect API
    // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
    // Reflect.get() 等同于 res = target[key]
    // Reflect 用来替代直接调用 Object 的方法
    const res = Reflect.get(target, key, receiver)
    // 省略
    if (!isReadonly) {
      // 核心,添加依赖收集
      track(target, TrackOpTypes.GET, key)
    }
    if (shallow) {
      return res
    }
    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
    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
  }
}
        track函数被定义在packages/reactivity/src/effect.ts文件中,这里的targetMap为WeakMap对象,该对象是一个弱引用类型
以传入的对象{ name: 'first', age: 18}作为key,value 值为Map对象,之后设置depsMap,key 当前为name,value为Set对象,最后执行trackEffects(dep, eventInfo):

trackEffects是用于依赖收集的核心函数,将当前活跃的副作用函数 activeEffect 与依赖集合 dep 关联起来,从而实现当依赖数据变化时能够触发副作用函数的重新执行

此时再看下targetMap对象数据:

这样就完成了数据的依赖收集,之后就可以通过指定对象指定属性获取到对应的 fn 方法 。而依赖收集本质上就是targetMap和ReactiveEffect之间的关联
createGetter执行完毕返回对应的值,当前为 first:

两秒后执行obj.name = 'last',触发set方法,该方法定义在packages/reactivity/src/baseHandlers.ts中,主要关注trigger(target, TriggerOpTypes.SET, key, value, oldValue)这行代码,键存在且新值与旧值不同,触发 set 类型的依赖更新

trigger方法
该方法被定义在packages/reactivity/src/effect.ts文件中:
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 根据传入的对象 获取 对应的 Map 对象
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      // 根据属性获取对应的 ReactiveEffect
      deps.push(depsMap.get(key))
    }
    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      // 省略
      // 当前为 set 类型
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }
  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined
  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}
        根据指定对象获取到对应的Map对象,此时depsMap为:

之后再根据指定属性获取对应的ReactiveEffect,再添加到deps中,此时deps为:

后面我们只需关注triggerEffects(deps[0], eventInfo) 这行代码, triggerEffects函数也packages/reactivity/src/effect.ts文件中:

可以看出triggerEffects函数实际先获取到 effect 数组,遍历数组执行每个 effect.run() ,实际执行的是 fn 方法 ,该方法是最初依赖收集时传入的匿名函数,之后再次触发getter方法,从而进行赋值,至此整个依赖触发完成

总结
1)reactive 函数与 createReactiveObject 方法
reactive函数实际执行了createReactiveObject方法,该方法主要创建了一个Proxy实例对象,给代理对象添加了getter和setter行为
getter和setter方法定义在mutableHandlers对象中,用于拦截对象属性的获取和设置操作
2)get 方法与依赖收集
get方法实际执行了createGetter方法,在这个过程中,track函数被调用,用于进行依赖收集
依赖收集的过程是构建一个WeakMap(targetMap)对象,完成指定对象obj的指定属性name到effect的依赖收集工作
3)effect 函数与 ReactiveEffect 实例
effect函数实际创建了一个ReactiveEffect实例,该构造函数接收一个fn函数(即传进来的匿名函数),该回调函数必须暴露getter行为
在ReactiveEffect的run函数中,给activeEffect赋值,并执行fn函数
4)依赖收集的具体过程
当访问obj.name时,getter被触发,激活track方法,该方法构建WeakMap(targetMap)对象,记录下当前活跃的副作用函数与obj.name的依赖关系
5)set 方法与依赖触发
set方法实际执行了createSetter方法,触发trigger函数进行依赖触发
trigger函数从之前targetMap依赖收集的对象中获取,根据key(即name)获取到对应的副作用函数,然后执行fn函数,从而完成一个依赖触发的过程
6)依赖触发的具体过程
当修改obj.name时,setter被触发,激活trigger方法,它通知所有依赖于obj.name的副作用函数重新执行,从而更新视图
reactive缺陷:
- 解构后不支持响应性
 - 不支持基本类型,只能是对象