当前位置: 首页 > 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.lryc.cn/news/615835.html

相关文章:

  • (LeetCode 面试经典 150 题) 104. 二叉树的最大深度 (深度优先搜索dfs)
  • 深入解析微服务分布式事务的原理与优化实践
  • 双非二本如何找工作?
  • CPP继承
  • 40.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--初始化网关
  • 【递归、搜索与回溯算法】递归算法
  • 【前端基础】14、CSS设置背景(background相关的)
  • Unity中实现自动寻路
  • 串口通信初始化过程是怎样的???
  • 每日五个pyecharts可视化图表-line:从入门到精通 (2)
  • go语言运算符
  • H3C(基于Comware操作系统)与eNSP平台(模拟华为VRP操作系统)的命令差异
  • GPT OSS深度解析:OpenAI时隔6年的开源模型,AI民主化的新里程碑?
  • 【递归、搜索与回溯算法】深度优先搜索
  • python Flask简单图书管理 API
  • 从Redisson源码角度深入理解Redis分布式锁的正确实现
  • Lua基础+Lua数据类型
  • Hadoop MapReduce过程
  • nginx+Lua环境集成、nginx+Lua应用
  • 分享一个基于Python和Hadoop的的电信客户特征可视化分析平台 基于Spark平台的电信客服数据存储与处理系统源码
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘mlflow’问题
  • leetcode2379:得到K个黑块的最少涂色次数(定长滑动窗口)
  • Boost.Asio io_service 与 线程 的分析
  • 字节:计算机存储单位
  • 算术运算符指南
  • 企业级WEB应用服务器TOMCAT — WEB技术详细部署
  • 使用Blender可视化多传感器坐标系转换
  • 从onnx模型到om模型的全自动化转化
  • 2025年APP开发趋势:4大方向重构行业格局
  • 【lucene】BlockDocsEnum 跟BlockImpactsDocsEnum 的区别