#前端重铸之路 Day5 🔥🔥🔥🔥🔥

70 阅读4分钟

前置涩话

掘金的小伙伴们大家好,这里是家里蹲选手sin~ 拖延症真是太严重啦,这个debuff真的是伴随一生啊(〝▼皿▼) 每天都是哄着自己放下手机起来学一点是一点。也是坚持了五天啦~ 浅浅夸一下自己吧(^-^)V sin太棒啦!那今天就带来比较烧脑的知识,vue3响应式原理的简单实现。١١(❛ᴗ❛)冲冲冲!!

✅ vue3响应式原理的简单实现

数据劫持

首先我们知道,vue2是使用Object.defineProperty()对数据进行劫持。Object.defineProperty()是对象的内部基本方法之一(操作的是对象的属性)。而且有一个缺点,对于通过下标修改数组或给对象新增属性时没有进行劫持,因为数组的增删会改变下标,劫持要重新遍历数组,这样造成的性能代价与用户收益不成正比。而vue3使用proxy实现数据响应式,proxy是对象所有内部基本方法的拦截器(直接操作对象)。可以捕获到对象或数组的所有改变,proxy并不能监听内部深层次的对象变化,而vue3处理方式是在getter中递归响应式,这样的好处就是真正访问到的内部对象才会变成响应式。

实现reactive

import {track, trigger} from './effect.js'

const isObject = (target) => target !== null&&typeof target == 'object'
export const reactive = (target) => {
    return new Proxy(target, {
        get(target,key,receiver){
            let res = Reflect.get(target,key,receiver)
            track(target, key)
            if(isObject(res)){
                return reactive(res)
            }
            return res
        },
        set(target,key,value,receiver){
            let res = Reflect.set(target,key,value,receiver)
            trigger(target, key)
            return res
        }
    })
}

实现 effect track trigger(响应式核心)

effect函数(副作用函数)

let activeEffect
export const effect = (fn) => {
  const _effect = function() {
    activeEffect = _effect  // 设置当前激活的副作用
    fn()                   // 执行原始函数
  }
  _effect()                // 立即执行一次
}
  • 功能:创建响应式副作用

  • 核心流程

    1. 定义内部函数 _effect

    2. 执行 _effect 时:

      • 将自身设为 activeEffect(当前激活的副作用)
      • 执行传入的回调函数 fn
    3. 立即执行一次 _effect 进行依赖收集

  • 关键点

    • activeEffect 是全局变量,存储当前正在执行的副作用
    • 当响应式数据被访问时,会将 activeEffect 收集为依赖

track函数(依赖追踪)

const targetMap = new WeakMap()
export const track = (target, key) => {
  let depsMap = targetMap.get(target)
  if(!depsMap){
      depsMap = new Map()
      targetMap.set(target, depsMap)
  }
  let deps = depsMap.get(key)
  if(!deps){
      deps = new Set()
      depsMap.set(key, deps)
  }
  deps.add(activeEffect)   //收集当前副作用
}
  • 数据结构
graph LR
targetMap --> |键:响应式对象| target
targetMap --> |值:Map| depsMap
depsMap --> |键:属性名| key
depsMap --> |值:依赖Set集合| deps
deps --> |值:effect函数| effect1
deps --> effect2
  • 核心流程

    1. 从 targetMap 中获取对象对应的依赖映射 depsMap
    2. 从 depsMap 中获取属性对应的依赖集合 deps
    3. 将当前激活的副作用 activeEffect 添加到依赖集合
  • 作用:建立「响应式对象属性 → 副作用函数」的映射关系

trigger函数(触发更新)

export const trigger = (target, key) => {
    const depsMap = targetMap.get(target)
    const deps = depsMap.get(key)
    deps.forEach(effect=>effect())
}
  • 核心流程

    1. 从 targetMap 中获取对象对应的 depsMap
    2. 从 depsMap 中获取属性对应的依赖集合 deps
    3. 遍历执行所有依赖的副作用函数
  • 作用:当响应式数据变化时,触发所有相关的副作用函数重新执行

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script type="module">
      import { reactive } from './reactive.js'
      import { effect } from './effect.js'
      const user = reactive({
          name: '小林忽悠',
          age: 23,
          a: {
              b: {
                  c: {
                      money: 1000000
                  }
              }
          }
      })

      effect(()=>{
          document.querySelector('#app').innerText = `${user.name} - ${user.age} - ${user.a.b.c.money}`
      })
      setTimeout(()=>{
          user.name = '大林忽悠'
          user.age = 26
          setTimeout(()=>{
            user.a.b.c.money = 9999999
          },1000)
      },2000)
    </script>
</body>
</html>

当前实现的局限性

  1. 缺少嵌套 effect 支持(Vue 3 使用 effect 栈解决)
  2. 未处理重复触发问题(需用调度队列)
  3. 缺少 cleanup 机制处理分支切换
  4. 没有处理数组等特殊对象

这个实现虽然简化,但完整展示了 Vue 3 响应式系统的核心思想:通过 effect 注册副作用,通过 track 收集依赖,通过 trigger 触发更新。实际 Vue 3 源码中还有更多优化和安全处理。

Proxy为什么要配合Reflect使用?

同样有参数receiver,触发代理对象的劫持时保证正确的 this 上下文指向。 Proxy 中接受的 Receiver 形参表示代理对象本身或者继承于代理对象的对象。 Reflect 中传递的 Receiver 实参表示修改执行原始操作时的 this 指向。 你可以简单的将 Reflect.get(target, key, receiver) 理解成为 target[key].call(receiver),不过这是一段伪代码,但是这样你可能更好理解。

本文学习自小满zs的vue3视频课程,仅做个人学习笔记

原视频地址:space.bilibili.com/99210573