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

Vue.js设计于实现 - 响应式(三)

副作用函数和响应式数据

  • 副作用函数

再次说明一下,主要读取或修改了公共变量的都是副作用函数
例如

function effect() {document.body.innerText = '123'
}
let val = 1
function effect2() {val = 2
}
  • 响应式数据

如果在一个副作用函数中读取或修改了某个对象的属性值,那么我们希望当值变化后,副作用函数自动重新执行,实现该目标,这个对象就是响应式对象

响应式数据基本实现

拦截一个对象的读取和设置操作,当读取字段时,将副作用函数存入一个“桶”中,当这个字段改变时,再执行副作用函数

// 存储副作用函数的桶
const bucket = new Set()
const data = {text: 'hello world'
}
const obj = new Proxy(data, {get(target, key) {// 收集依赖bucket.add(effect)// 返回属性值return target[key]},set(target, key, value) {target[key] = value// 触发副作用函数bucket.forEach(fn => fn())// 返回 true 表示设置成功return true}
})
  • 实现注册副作用函数

上面的内容只是一个最小型的响应式结构,实际上我们的副作用函数不会都叫effect,那么如何实现不论如何命名函数都能收集依赖呢,这里采用一个全局变量activeEffect来保存

let activeEffect
function effect (fn) {activeEffect = fnfn()
}
// 我们执行自己的函数时,使用effect调用
const changeBody = () => {document.body.innerText = obj.text
}
effect(changeBody)

那么getter的代码就要改为

get(target, key) {// 收集依赖if(activeEffect) {bucket.add(activeEffect)}// 返回属性值return target[key]
},
  • 实现副作用函数与对象的具体属性关联

在上面的内容中,当我们修改obj中的其他属性,并且该属性不在副作用函数中时,副作用函数依然会执行,因此要重新设计“桶”结构,添加映射关系

			-- 键 -- [函数]
对象   -- 键 -- [函数]-- 键 -- [函数]weakMap: {obj1: Map1,obj2: Map2
}
Map1: {key1: [ fn1, fn2 ],key2: [ fn1 ]
}
const data = { text: 'hello world' }// 临时保存副作用函数
let activeEffect
function effect (fn) {activeEffect = fnfn()
}
// 存放依赖的桶
const bucket = new WeakMap()
const obj = new Proxy(data, {get (target, key) {// 触发依赖收集track(target, key)return target[key]},set (target, key, value) {target[key] = value// 触发副作用函数trigger(target, key)}
})
function track (target, key) {if (!activeEffect) returnlet depsMap = bucket.get(target)if (!depsMap) {// 没有对象存对象bucket.set(target, (depsMap = new Map()))}let deps = depsMap.get(key)if (!deps) {// 没有对象的键存对象的键depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)
}
function trigger (target, key) {// 获取obj[key]执行副作用函数const depsMap = bucket.get(target)if (!depsMap) returnconst effects = depsMap.get(key)effects && effects.forEach(fn => fn())
}

这里使用weakMap不使用Map的原因是,weakMap对key是弱引用,一旦key被垃圾回收,则对应的键和值就访问不到了,避免内存溢出

  • 实现cleanup

如果副作用函数是一个三元表达式

function fn () {obj.ok ? obj.text : ''
}

那么当ok为false时,为了避免不必要的更新,我们需要清除obj.text的依赖收集,变为true时再重新依赖

解决方法为,在每次副作用函数执行前,将其从相关联的依赖集合中移除,后续执行过程中不读取该属性就不会进行收集

我们要重新设计副作用函数effect,在内部定义了新的effectFn函数,并为其添加deps属性,用来存储当前副作用函数的依赖集合

let activeEffect
function effect(fn) {const effectFn = () => {activeEffect = effectFnfn()}// 用来存储当前副作用函数的依赖集合effectFn.deps = []effectFn()
}

在track中收集副作用函数对应的键 对应的函数集合

function track (target, key) {if (!activeEffect) returnlet depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将对应键的对应函数集合存入副作用函数的deps数组中activeEffect.deps.push(deps)
}

这样在副作用函数执行前遍历deps,删除其中包含自身的,即可实现移除

function cleanup(effectFn) {for(let i = 0; i < effectFn.deps.length; i++) {// deps为Set集合const deps = effectFn.deps[i]// 删除自身deps.delete(effectFn)}// 重置effectFn.deps数组effectFn.deps.length = 0
}
let activeEffect
function effect(fn) {const effectFn = () => {// 调用清除cleanup(effectFn)activeEffect = effectFnfn()}// 用来存储当前副作用函数的依赖集合effectFn.deps = []effectFn()
}

此时运行代码会无限循环,原因是trigger使用了forEach遍历,在语言规范中,当forEach遍历Set集合时,如果一个值已经被访问过了,但该值被删除并重新添加到集合,如果此时forEach遍历没有结束,那么该值会被重新访问
即这个代码会无限循环

const set = new Set([1])
set.forEach(item => {set.delete(1)set.add(1)console.log('这段代码会无限循环')
})

因此要修改trigger

function trigger(target, key) {const depsMap = buckect.get(target)if(!depsMap) returnconst effects = depsMap.get(key)// 创建中间变量用于执行const effectToRun = new Set(effects) effectToRun.forEach(effectFn => effectFn())
}

目前的代码为

const data = { text: 'hello world' }// 清除副作用函数
function cleanup(effectFn){for(let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]// 删除掉所有Set中的自身deps.delete(effectFn)}// 清空副作用函数的依赖集合effectFn.deps.length = 0
}// 临时保存副作用函数
let activeEffect
function effect (fn) {const effectFn = () => {// 执行副作用函数前删除集合中的自身cleanup(effectFn)activeEffect = effectFnfn()}// 存储当前副作用函数依赖集合effectFn.deps = []effectFn()
}
// 存放依赖的桶
const bucket = new WeakMap()
const obj = new Proxy(data, {get (target, key) {// 触发依赖收集track(target, key)return target[key]},set (target, key, value) {target[key] = value// 触发副作用函数trigger(target, key)}
})
function track (target, key) {if (!activeEffect) returnlet depsMap = bucket.get(target)if (!depsMap) {// 没有对象存对象bucket.set(target, (depsMap = new Map()))}let deps = depsMap.get(key)if (!deps) {// 没有对象的键存对象的键depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将当前副作用函数存入依赖集合activeEffect.deps.push(deps)
}
function trigger (target, key) {// 获取obj[key]执行副作用函数const depsMap = bucket.get(target)if (!depsMap) returnconst effects = depsMap.get(key)// 创建中间变量避免无限循环const effectsToRun = new Set(effects)effectsToRun.forEach(effectFn => effectFn())// effects && effects.forEach(fn => fn())
}
http://www.dtcms.com/a/324687.html

相关文章:

  • Spring Boot 全局异常处理与日志监控实战
  • OneCode 3.0 可视化功能全面分析:从开发者到用户的全场景解析
  • 一周学会Matplotlib3 Python 数据可视化-绘制条形图(Bar)
  • 论文复现与分析内容关于一种实用的车对车(V2V)可见光通信(VLC)传播模型
  • Z20K118库中寄存器及其库函数封装-REGFILE库
  • Windows执行kubectl提示拒绝访问【Windows安装k8s】
  • imx6ull-驱动开发篇17——linux原子操作实验
  • PXE自动化安装部署OpenEuler24.03LTS
  • MySQL中的in和exists的区别
  • mmdetection3d中centerpoint解析
  • FPGA常用资源之IO概述
  • Mybatis学习之动态SQL(八)
  • 使用GLib D-Bus 库创建dbus服务端
  • 安全运维的核心
  • 使用 iFLOW-CLI GitHub Action 和 Qwen3-Coder 给 GitHub 仓库生成幻灯片风格的文档站点
  • 一个基于 Next.js 和 Puppeteer 的 Markdown 转图片服务,支持 Docker 部署和 API 集成
  • AI绘画:生成唐初秦叔宝全身像提示词
  • reuse: for booting my spring project with mvn in Windows command line
  • 理清C语言中内存操作的函数
  • LeetCode_字符串
  • 《C语言》结构体和联合体练习题--1
  • ROS2 学习笔记
  • 基于百度地图API的社区地图展示技术实现
  • 初识STL
  • ADB简介
  • 【redis初阶】--------Set 集合类型
  • Baumer高防护相机如何通过YoloV8深度学习模型实现道路坑洼的检测识别(C#代码UI界面版)
  • Linux操作系统从入门到实战(十六)冯诺依曼体系结构,操作系统与系统调用和库函数概念
  • API 接入终极指南:实时掌握京东商品动态
  • openpnp - 顶部相机如果超过6.5米影响通讯质量,可以加USB3.0信号放大器延长线