当前位置: 首页 > article >正文

《Vue进阶教程》第十四课:改进桶结构

 往期内容:

《Vue进阶教程》第三课:Vue响应式原理

《Vue进阶教程》第四课:reactive()函数详解

《Vue进阶教程》第五课:ref()函数详解(重点)

《Vue进阶教程》第六课:computed()函数详解(上)

《Vue进阶教程》第七课:computed()函数详解(下)

《Vue进阶教程》第八课:watch()函数的基本使用

《Vue进阶教程》第九课:watch()函数的高级使用

《Vue进阶教程》第十课:其它函数

《Vue进阶教程》第十一课:响应式系统介绍

《Vue进阶教程》第十二课:实现一对多

《Vue进阶教程》第十三课:实现依赖收集

一、改进桶结构

看起来现在可以自动收集依赖. 但是依然解决不了不同的属性对应不同的副作用函数集合这个问题.

因此, 我们需要改进桶结构

改造成一个Map映射表, 不同的属性对应不同的Set集合

示例

reactive.js

// 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
const bucket = new Map() // 修改 [name: Set(fn, fn), age: Set(fn, fn)]

// 定义一个全局变量, 保存当前正在执行的副作用函数
let activeEffect = null

function isObject(value) {
  return typeof value === 'object' && value !== null
}

// 收集依赖
function track(target, key) {
  if (!activeEffect) return
  
  let depSet = bucket.get(key)
  if (!depSet) {
    depSet = new Set()
    bucket.set(key, depSet)
  }
  
  // 只有activeEffect有值时(保存的副作用函数), 才添加到桶中
  depSet.add(activeEffect)
}

function trigger(target, key) {
  // 从副作用函数桶中依次取出每一个元素(副作用函数)执行
  let depSet = bucket.get(key)
  if (depSet) {
    depSet.forEach((fn) => fn())
  }
}
/**
* 创建响应式数据
*  @param [object]: 普通对象
*  @return [Proxy]: 代理对象
*/
function reactive(data) {
  if (!isObject(data)) return
  
  return new Proxy(data, {
    get(target, key) {
      // 在get操作时, 收集依赖
      track(target, key)
      
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      // 在set操作时, 触发副作用重新执行
      trigger(target, key)
      return true
    },
  })
}

/**
* 注册副作用函数
*  @param [function]: 需要注册的 副作用函数
*/
function effect(fn) {
  if (typeof fn !== 'function') return
  
  // 记录正在执行的副作用函数
  activeEffect = fn
  // 调用副作用函数
  fn()
  // 重置全局变量
  activeEffect = null
}

至此, 我们通过改进桶结构, 可以区分同一个代理对象的不同的属性

二、进一步改进桶结构

🤔思考

如果不同的源对象存在同名属性, 就会出现问题

比如

pState代理的源对象上存在name属性

pState1代理的源对象上也存在name属性

这样, 在bucket桶里, 就不能区分不同的代理对象

问题示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // 定义一个副作用桶bucket
      const bucket = new Map() // 修改
      // 定义一个全局变量, 作于保存 `当前副作用函数`
      let activeEffect = null

      // 收集依赖
      function track(target, key) {
        // 根据不同的key, 获取对应的集合
        let depSet = bucket.get(key)
        if (!depSet) {
          // 如果不存在, 创建一个新的集合, 并添加到桶中
          depSet = new Set()
          bucket.set(key, depSet) // 建立 key -> Set的对应关系
        }
        depSet.add(activeEffect) // 将当前副作用函数添加到对应的集合
      }

      function trigger(target, key) {
        let depSet = bucket.get(key)

        if (depSet) {
          // 如果对应的集合存在, 遍历集合中的每个函数
          depSet.forEach((fn) => fn())
        }
      }

      /**
       * 定义响应式
       *  @param [object] : 普通对象
       *  @return [Proxy] : 代理对象
       */
      function reactive(data) {
        // 如果传入的data不是一个普通对象, 不处理
        if (typeof data !== 'object' || data == null) return

        return new Proxy(data, {
          get(target, key) {
            // console.log(`自定义访问${key}`)
            if (activeEffect != null) {
              // 收集依赖
              track(target, key)
            }

            return target[key]
          },
          set(target, key, value) {
            // console.log(`自定义设置${key}=${value}`)
            target[key] = value // 先更新值
            // 触发更新
            trigger(target, key)
            return true
          },
        })
      }

      /**
       * 注册副作用函数
       * @params [function]: 要注册的 副作用函数
       */
      function registEffect(fn) {
        if (typeof fn !== 'function') return

        // 将当前注册的副作用函数 保存 到全局变量中
        activeEffect = fn
        // 执行当前副作用函数, 收集依赖
        fn()
        // 重置全局变量
        activeEffect = null
      }

      const pState = reactive({ name: 'hello', age: 20 })

      const pState1 = reactive({ name: 'p1' })

      registEffect(function effectFn() {
        console.log('effectFn...', pState.name)
      })

      registEffect(function effectFn1() {
        console.log('effectFn1...', pState1.name)
      })

      console.log(bucket)
      setTimeout(() => {
        pState.name = 'brojie'
      }, 1000)
    </script>
  </body>
</html>

解决方案

示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      // 定义一个副作用桶bucket
      const bucket = new WeakMap() // 修改
      // 定义一个全局变量, 作于保存 `当前副作用函数`
      let activeEffect = null

      // 收集依赖
      function track(target, key) {
        // 根据不同的target, 获取对应的Map
        let depMap = bucket.get(target)
        if (!depMap) {
          depMap = new Map()
          bucket.set(target, depMap) // 建立target -> Map的对应关系
        }
        // 根据不同的key, 获取对应的集合
        let depSet = depMap.get(key)
        if (!depSet) {
          // 如果不存在, 创建一个新的集合
          depSet = new Set()
          depMap.set(key, depSet) // 建立 key -> Set的对应关系
        }
        depSet.add(activeEffect) // 将当前副作用函数添加到对应的集合
      }

      function trigger(target, key) {
        let depMap = bucket.get(target)

        if (!depMap) return

        let depSet = depMap.get(key)

        if (depSet) {
          // 如果对应的集合存在, 遍历集合中的每个函数
          depSet.forEach((fn) => fn())
        }
      }

      /**
       * 定义响应式
       *  @param [object] : 普通对象
       *  @return [Proxy] : 代理对象
       */
      function reactive(data) {
        // 如果传入的data不是一个普通对象, 不处理
        if (typeof data !== 'object' || data == null) return

        return new Proxy(data, {
          get(target, key) {
            // console.log(`自定义访问${key}`)
            if (activeEffect != null) {
              // 收集依赖
              track(target, key)
            }

            return target[key]
          },
          set(target, key, value) {
            // console.log(`自定义设置${key}=${value}`)
            target[key] = value // 先更新值
            // 触发更新
            trigger(target, key)
            return true
          },
        })
      }

      /**
       * 注册副作用函数
       * @params [function]: 要注册的 副作用函数
       */
      function registEffect(fn) {
        if (typeof fn !== 'function') return

        // 将当前注册的副作用函数 保存 到全局变量中
        activeEffect = fn
        // 执行当前副作用函数, 收集依赖
        fn()
        // 重置全局变量
        activeEffect = null
      }

      const pState = reactive({ name: 'hello', age: 20 })

      registEffect(function effectName() {
        console.log('effectName...', pState.name)
      })

      registEffect(function effectAge() {
        console.log('effectAge...', pState.age)
      })

      console.log(bucket)
      setTimeout(() => {
        pState.name = 'brojie'
      }, 1000)
    </script>
  </body>
</html>

至此, 我们可以区分不同的代理对象下不同属性对应的副作用函数集合.
真正实现了完善的桶结构~✌
如果学到这里, 恭喜你, 已经实现了一个可用的响应式系统! ^_^😃


三、小结


💡 小结
1在get时收集依赖: 收集不同代理对象不同属性所依赖的副作用函数
2在set时触发依赖: 取出当前属性所依赖的所有副作用函数, 重新执行


http://www.kler.cn/a/441512.html

相关文章:

  • OpenStack基础架构
  • skynet 源码阅读 -- 启动主流程
  • Linux下 date时间应该与系统的 RTC(硬件时钟)同步
  • vector的使用,以及部分功能的模拟实现(C++)
  • 详细介绍:Kubernetes(K8s)的技术架构(核心概念、调度和资源管理、安全性、持续集成与持续部署、网络和服务发现)
  • 【Rabbitmq】Rabbitmq高级特性-发送者可靠性
  • 基于微信小程序的小区疫情防控ssm+论文源码调试讲解
  • 【RK3588 Linux 5.x 内核编程】-内核中断与SoftIRQ
  • 常见排序算法总结 (五) - 堆排序与堆操作
  • 谷歌推出 AI 编码助手 “Jules”,自动修复软件漏洞加速开发
  • linux中的权限简单总结
  • 蓝桥杯刷题——day3
  • ElasticSearch的自动补全功能(拼音分词器、自定义分词器、DSL实现自动补全查询、RestAPI实现自动补全查询)
  • npm、yarn、pnpm 设置最新国内镜像源(附官方镜像源和最新阿里源)
  • vue下载node包,前端编译报错 ‘ ./node modules/ml-matrix/src/symmetricMatrix.js‘
  • 基于Spring Boot的小区车辆管理系统
  • 如何用重构解锁高效 Vue 开发之路
  • C++ #和##的用法
  • 【C++算法】45.分治_快排_数组中的第K个最大元素
  • Linux Nice 优先级功能解析
  • vscode无密码远程登录,不用输密码
  • 2024-2030全球与中国AI养猪解决方案市场现状及未来发展趋势
  • Flutter:页面中触发点击事件,通过id更新特定视图
  • Unreal的Audio::IAudioCaptureStream在Android中录制数据异常
  • 31.攻防世界php_rce
  • 被裁20240927 --- YOLO 算法