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

《Vue进阶教程》第十六课:深入完善响应式系统之单例模式

   往期内容:

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

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

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

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

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

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

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

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

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

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

《Vue进阶教程》第十五课:深入完善响应式系统之模块化

🤔思考

  1. 对于同一个源对象每次调用reactive返回的代理对象应该是一样的
  2. 对于一个已经代理过的对象再次代理应该返回的也应该是一样的

1) 实现单例

为了实现单例, 我们需要建立源对象->代理对象的映射关系

  • 如果存在映射, 说明已经代理过了, 直接返回
  • 如果不存在映射, 说明没有代理过, 创建一个新的代理对象返回

定义源对象->代理对象的映射表(使用WeakMap)

reactive.js

// 定义一个副作用函数桶, 存放所有的副作用函数. 每个元素都是一个副作用函数
// 修改 [state -> Map[name: Set(fn, fn), age: Set(fn, fn)], state1 -> Map]
const bucket = new WeakMap()// 建立一个映射表 target -> proxy
const reactiveMap = new WeakMap() // 新增// 定义一个全局变量, 保存当前正在执行的副作用函数
let activeEffect = nullfunction isObject(value) {return typeof value === 'object' && value !== null
}// 收集依赖
function track(target, key) {// 只有activeEffect有值时(保存的副作用函数), 才添加到桶中if (!activeEffect) returnlet depMap = bucket.get(target)if (!depMap) {depMap = new Map()bucket.set(target, depMap)}let depSet = depMap.get(key)if (!depSet) {depSet = new Set()depMap.set(key, depSet)}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) {if (!isObject(data)) return// 如果映射表中存在了对应关系if (reactiveMap.has(data)) {// 返回data对应的代理对象return reactiveMap.get(data)}const proxy = 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},})// 建立data(源对象)和proxy(代理对象)的映射关系reactiveMap.set(data, proxy)return proxy
}/*** 注册副作用函数*  @param [function]: 需要注册的 副作用函数*/
function effect(fn) {if (typeof fn !== 'function') return// 记录正在执行的副作用函数activeEffect = fn// 调用副作用函数fn()// 重置全局变量activeEffect = null
}

 测试用例

<!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><script src="./reactive.js"></script></head><body><script>const source = { name: 'hello' }const p = reactive(source)const p1 = reactive(source)console.log(p === p1) // true</script></body>
</html>

2) 实现重复代理

可以定义一个特殊的标识__v_isReactive

  • 如果存在该标识, 说明已经代理过, 直接返回
  • 如果不存在该标识, 说明没有被代理, 创建新的代理对象

示例

// 定义源对象->代理对象映射表
const reactiveMap = new WeakMap()// 定义一个副作用桶bucket
const bucket = new WeakMap() 
// 定义一个全局变量, 作于保存 `当前副作用函数`
let activeEffect = null// 收集依赖
function track(target, key) {// 根据不同的target, 获取对应的Maplet depMap = bucket.get(target)if (!depMap) {depMap = new Map()bucket.set(target, depMap)}let depSet = depMap.get(key)if (!depSet) {depSet = new Set()depMap.set(key, depSet)}depSet.add(activeEffect)
}// 触发执行
function trigger(target, key) {let depMap = bucket.get(target)if (!depMap) returnlet depSet = depMap.get(key)if (depSet) {// 如果对应的集合存在, 遍历集合中的每个函数depSet.forEach((fn) => fn())}
}/*** 定义响应式*  @param [object] : 普通对象*  @return [Proxy] : 代理对象*/
export function reactive(data) {// 如果传入的data不是一个普通对象, 不处理if (typeof data !== 'object' || data == null) returnif (reactiveMap.has(data)) {// 返回data对应的代理对象return reactiveMap.get(data)}// 如果存在标识, 说明data被代理过了if (data['__v_isReactive']) {return data}const proxy = new Proxy(data, {get(target, key) {if (key == '__v_isReactive') return true // 新增// 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},})reactiveMap.set(data, proxy)return proxy
}/*** 注册副作用函数* @params [function]: 要注册的 副作用函数*/
export function effect(fn) {if (typeof fn !== 'function') return// 将当前注册的副作用函数 保存 到全局变量中activeEffect = fn// 执行当前副作用函数, 收集依赖fn()// 重置全局变量activeEffect = null
}

 测试用例

<!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><script src="./reactive.js"></script></head><body><script>const source = { name: 'hello' }const p = reactive(source)const p1 = reactive(p)console.log(p === p1) // true</script></body>
</html>

http://www.lryc.cn/news/508171.html

相关文章:

  • C语言版解法力扣题:将整数按权重排序
  • Unity ECS和OOP优劣对比
  • 【Java基础面试题026】Java中的String、StringBuffer和StringBuilder的区别是什么?
  • 解析在OceanBase创建分区的常见问题|OceanBase 用户问题精粹
  • Flutter组件————Container
  • Java重要面试名词整理(二):SpringMyBatis
  • Excel生成DBC脚本源文件
  • Git进阶:本地或远程仓库如何回滚到之前的某个commit
  • linux 中文输入法设置的宏观思路 (****)
  • 271-基于XC7V690T的12路光纤PCIe接口卡
  • Semaphore UI安装和实践
  • Redis篇--常见问题篇7--缓存一致性2(分布式事务框架Seata)
  • Docker Compose 安装 Harbor
  • 使用docker compose安装gitlab
  • 大模型日报 2024-12-18
  • Linux安装mysql5.7
  • 【容器】k8s学习笔记原理详解(十万字超详细)
  • .NET重点
  • SMMU软件指南SMMU编程之虚拟机结构和缓存
  • Go 语言并发实战:利用协程处理多个接口进行数据融合
  • Redis Hash Tag 知识详解
  • 在 Ubuntu 上安装 Muduo 网络库的详细指南
  • Golang Gin Redis+Mysql 同步查询更新删除操作(我的小GO笔记)
  • nodejs搭配express网站开发后端接口设计需要注意事项
  • mysql 基于chunk机制是如何支持运行期间,动态调整buffer pool大小的
  • 智能客户服务:AI与大数据的革新力量
  • Python日常使用的自动化脚本
  • 代理模式(JDK,CGLIB动态代理,AOP切面编程)
  • 【Leetcode 热题 100】236. 二叉树的最近公共祖先
  • Go框架比较:goframe、beego、iris和gin