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

vue.js的设计与实现(响应系统1)

文章目录

    • 概要
    • 响应式数据与副作用函数
    • 响应式数据的基本实现
    • 设计一个完善的响应式系统
    • 小结

概要

响应系统式vue的重要组成部分,我们都知道vue3中采用了proxy实现响应式数据的,那是怎么实现的呢?我们往下看

响应式数据与副作用函数

大家肯定会问:什么是副作用函数呢?如以下代码:

function effect(){document.body.innerText = 'hello vue3'
}

当effect函数执行时,他会设置body的文本内容,但除了effect函数之外的任何函数都可能会读取或设置body的文本内容。也就是说effect函数的执行会直接或者间接影响到其他函数的执行,这时我们就会说effect函数产生了副作用。

理解了副作用函数,我们再说说什么是响应式数据,看一下代码

const obj = {text:'hello word'}
function effect(){document.body.innerText = obj.text
}
obj.text = 'hello vue3'

看以上代码,effect中设置body的文本内容是使用了obj中text的值,当我们去改变text值的时候,我们会希望副作用effect函数要重新执行,但是很明显,以上代码无法做到这点。

响应式数据的基本实现

我们要怎么让obj变成响应式数据呢?想到这里,大家都会说用proxy把!没错就是用proxy,那具体要怎么做呢?

我们知道了改变obj的text值的时候,需要重新执行effect函数。我们可以观察到 effect函数中 有读取obj.text,这就会出发proxy的get操作,当哦我们修改obj.text值时,就会触发set操作。当然,我们可能不仅仅只有一个effect这个副作用函数,可能会有多个,这个时候我们会有什么办法呢?对的,我们可以把出发get操作的所有副作用函数存起来,在触发set操作时,拿出来全部执行一遍,那我们就会想到一下的写法:

const bucket = new Set()
const data = {text:'hello word'}
const obj = new Proxy(data,{get(target,key){bucket.add(effect)return target[key]},set(target,key,newVal){target[key] = newValbucket.forEach(fn=>fn())return true}
})

这里为什么需要用Set呢?就是为了使用Set的特性去重,因为我们每次执行effect的时候 都会执行一次get方法,如果我们使用正常Array来存储的话,那是不是会执行两次effect副作用函数了呢?
这样 我们在执行一次以下代码,看下是不是我们想要的效果

function effect (){document.body.innerText = obj.text
}
effect()
setTimeout(()=>{obj.text = 'hello vue3'
},1000)

我们会发现 当改变obj.text时,又执行了一次effect副作用函数,把body的文本内容更改了

设计一个完善的响应式系统

通过上面,我们以及实现了一个简单的响应式了,我们会发现直接通过名字effect来获取副作用函数,这种编码的方式不够灵活。副作用函数的名字我们应该是要可以随便取的,

所以我们要对以上的代码进行一个改造,我们可以把effect函数赋值给一个变量,这样我们就不会限制副作用函数的命名了。如以下代码:

//这里我们声明一个全局变量来存储被注册的副作用函数
let activeEffect
function effect(fn){activeEffect = fnfn()
}
//effect函数的使用方法就会变成传入一个函数为参数
effect(()=>{document.body.innerText = obj.text
})
//我们在注册proxy代理的时候 就应该把effect变成activeEffect
const bucket = new Set()
const data = {text:'hello word'}
const obj = new Proxy(data,{get(target,key){if(activeEffect){ //这里需要判断一下是否存在副作用函数,存在就加进去,不存在就不需要bucket.add(activeEffect)}return target[key]},set(target,key,newVal){target[key] = newValbucket.forEach(fn=>fn())return true}
})

你觉得这个就是vue3响应式的原理的吗?不远远没有这么简单,我们可以随便测试一下,如果我们添加一个obj里面不存在的属性,也会执行这个副作用函数,因为我们的副作用函数没有和具体的key做绑定,只要有执行set方法,就会执行副作用函数,即使没有读取改变的属性。我们想要的对应关系应该如下:

target->key->effectFn
我们只有改变对应的key值才会执行对应key的副作用函数,那我们就对这个 bucket 存储副作用函数的地方进行改造,如一下代码:

const bucket = new WeakMap()
const data = {text:'hello word'}
const Obj = new Proxy(data,{get(target,key){if(!activeEffect){return target[key]}const desMap =  bucket.get(target)if(!desMap){bucket.set(target,desMap=new Map())}const deps = desMap.get(key)if(!deps){desMap.set(key,deps = new Set())}deps.add(activeEffect)return target[key]},set(target,key,newVal){target[key] = newValconst depsMap = bucket.get(target)if(!depsMap ) return const effect = depsMap.get(key)effect && effect.forEach(fn=>fn())return true}
})

这里可能就有同学要问了,为什么这里要使用weakMap呢?因为:weakMap是弱引用,当它的key值不在被调用的的时候,会被垃圾回收机制回收掉,举一个很简单的例子

const weakmap = new WeakMap()
(function (){const foo ={foo:1}weakmap.set(foo,1)
})()

当我们执行完这个代码后,foo会立刻被垃圾回收机制回收掉,因为weakmap是一个弱引用,不会影响垃圾回收机制回收垃圾,当foo从内存中移除,我们便无法weakmap的key值,也就无法通过weakmap获取对象foo。

我们再把上面的代码稍微优化一下,做下封装处理,能够更好的复用:

const Obj = new Proxy(data,{get(target,key){track(target,key)return target[key]},set(target,key,newVal){target[key] = newValtrigger(target,key)}
})
function track(target,key) {if(!activeEffect) return const desMap =  bucket.get(target)if(!desMap){bucket.set(target,desMap=new Map())}const deps = desMap.get(key)if(!deps){desMap.set(key,deps = new Set())}deps.add(activeEffect)
}
function trigger(target,key){const depsMap = bucket.get(target)if(!depsMap ) return const effects = depsMap.get(key)effects && effects.forEach(fn=>fn())
}

小结

以上就是基础的依赖收集,当然还是会有问题的,下一篇文章见,有错误的地方,希望给位同学可以指出,相互探讨!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【嵌入式】总结指南——Linux下的裸机驱动开发
  • docker的安装+docker镜像的基本操作
  • 浅谈垃圾回收机制
  • Python实现贪心算法
  • Python3:多行文本内容转换为标准的cURL请求参数值
  • UDP+TCP
  • leetcode242:有效的字母异位词
  • 【精选】基于协同过滤算法的小说推荐系统(定制参考分享)
  • 【51单片机】ds18b20驱动,11.0592MHZ,使用DS18b20
  • 【运维】linux使用systemd手动部署与管理服务进程,以webhook回调告警为例(附常用linux进程/端口状况查看命令)
  • C#发邮件时如何确保邮件内容的安全和隐私?
  • 猫用空气净化器好不好?养猫推荐宠物空气净化器品牌
  • uniapp点击预览图片,两种效果
  • ES6解构赋值详解;全面掌握:JavaScript解构赋值的终极指南
  • 聚类分析|距离与相似系数|层次聚类|K均值聚类|SPSS及Matlab
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • Apache Pulsar 2.1 重磅发布
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • co模块的前端实现
  • export和import的用法总结
  • Flannel解读
  • Git学习与使用心得(1)—— 初始化
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Laravel Mix运行时关于es2015报错解决方案
  • learning koa2.x
  • Objective-C 中关联引用的概念
  • TCP拥塞控制
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 利用jquery编写加法运算验证码
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 使用 QuickBI 搭建酷炫可视化分析
  • 使用common-codec进行md5加密
  • ​queue --- 一个同步的队列类​
  • #大学#套接字
  • (3)医疗图像处理:MRI磁共振成像-快速采集--(杨正汉)
  • (39)STM32——FLASH闪存
  • (LeetCode C++)盛最多水的容器
  • (libusb) usb口自动刷新
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (回溯) LeetCode 77. 组合
  • (排序详解之 堆排序)
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)我也是一只IT小小鸟
  • ***监测系统的构建(chkrootkit )
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET Core 中的路径问题
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .net 微服务 服务保护 自动重试 Polly
  • .NET企业级应用架构设计系列之开场白
  • @FeignClient注解,fallback和fallbackFactory