Vue3响应式原理 私

响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。

响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。

名词解释:

  • **副作用函数:**函数的执行会直接或间接影响其他函数的执行,这时我们说函数产生了副作用。副作用很容易产生,例如一个函数修改了全局变量,这其实也是一个副作用。
  • targetmap 是一个 weakmap 类型的集合,用来存储副作用函数,从类型定义可以看出 targetmap的数据结构方式:

weakmap 由 target --> map 构成
map 由 key --> set 构成

  • 其中 weakmap 的键是原始对象 target,weakmap 的值是一个 map 实例,map 的键是原始对象 target 的 key,map 的值是一个由副作用函数组成的 set。
  • effect函数:创建一个副作用函数,接受两个参数,分别是用户自定义的fn函数和options 选项。
  • track收集依赖:访问数据的时候,触发get函数,get函数最核心的部分是执行track函数收集依赖。这其实是一种懒操作。
  • trigger派发更新:当对属性进行赋值时,会触发代理对象的 set 拦截函数执行。
     

track函数收集依赖的实现


export function track(target: object, type: trackoptypes, key: unknown) {// 如果开启了依赖收集并且有正在执行的副作用,则收集依赖if (shouldtrack && activeeffect) {// 在 targetmap 中获取对应的 target 的依赖集合let depsmap = targetmap.get(target)if (!depsmap) {// 如果 target 不在 targetmap 中,则将当前 target 添加进 targetmap 中,并将 targetmap 的 value 初始化为 new map()。targetmap.set(target, (depsmap = new map()))}// 从依赖集合中获取对应的 key 的依赖let dep = depsmap.get(key)if (!dep) {// 如果 key 不存在,将这个 key 作为依赖收集起来,并将依赖初始化为 new set()depsmap.set(key, (dep = createdep()))}// 最后调用 trackeffects收集副作用函数,将副作用函数收集到依赖集合depsmap中。const eventinfo = __dev__? { effect: activeeffect, target, type, key }: undefinedtrackeffects(dep, eventinfo)}
}

trackeffects 函数

收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。

// 收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
export function trackeffects(dep: dep,debuggereventextrainfo?: debuggereventextrainfo
) {let shouldtrack = falseif (effecttrackdepth <= maxmarkerbits) {if (!newtracked(dep)) {dep.n |= trackopbit // set newly trackedshouldtrack = !wastracked(dep)}} else {// full cleanup mode.// 如果依赖中并不存当前的 effect 副作用函数shouldtrack = !dep.has(activeeffect!)}if (shouldtrack) {// 将当前的副作用函数收集进依赖中dep.add(activeeffect!)// 并在当前副作用函数的 deps 属性中记录该依赖activeeffect!.deps.push(dep)if (__dev__ && activeeffect!.ontrack) {activeeffect!.ontrack(object.assign({effect: activeeffect!},debuggereventextrainfo))}}
}

trigger 派发更新

对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:

const obj = { foo: 1 } 
//通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行
const p = new proxy(obj, {// 拦截设置操作set(target, key, newval, receiver){// 如果属性不存在,则说明是在添加新属性,否则设置已有属性const type = object.prototype.hasownproperty.call(target,key) ?  'set' : 'add'    // 设置属性值const res = reflect.set(target, key, newval, receiver)// 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数trigger(target,key,type)   return res}// 省略其他拦截函数
})p.foo = 2

trigger 函数

根据target和key, 从targetMap中找到相关的所有副作用函数遍历执行一遍。

export function trigger(target: object,type: triggeroptypes,key?: unknown,newvalue?: unknown,oldvalue?: unknown,oldtarget?: map<unknown, unknown> | set<unknown>
) {
//首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。const depsmap = targetmap.get(target)// 该 target 从未被追踪,则不继续执行if (!depsmap) {// never been trackedreturn}// 接着创建一个 set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。let deps: (dep | undefined)[] = []//接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。//如果操作类型是 triggeroptypes.clear ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。if (type === triggeroptypes.clear) {// collection being cleared// trigger all effects for target// 当需要清除依赖时,将当前 target 的依赖全部传入deps = [...depsmap.values()]} else if (key === 'length' && isarray(target)) {// 处理数组的特殊情况depsmap.foreach((dep, key) => {// 如果对应的长度, 有依赖收集需要更新if (key === 'length' || key >= (newvalue as number)) {deps.push(dep)}})} else {// schedule runs for set | add | delete// 在 set | add | delete 的情况,添加当前 key 的依赖if (key !== void 0) {deps.push(depsmap.get(key))}// also run for iteration key on add | delete | map.setswitch (type) {case triggeroptypes.add:if (!isarray(target)) {deps.push(depsmap.get(iterate_key))if (ismap(target)) {// 操作类型为 add 时触发map 数据结构的 keys 方法的副作用函数重新执行deps.push(depsmap.get(map_key_iterate_key))}} else if (isintegerkey(key)) {// new index added to array -> length changesdeps.push(depsmap.get('length'))}breakcase triggeroptypes.delete:if (!isarray(target)) {deps.push(depsmap.get(iterate_key))if (ismap(target)) {// 操作类型为 delete 时触发map 数据结构的 keys 方法的副作用函数重新执行deps.push(depsmap.get(map_key_iterate_key))}}breakcase triggeroptypes.set:if (ismap(target)) {deps.push(depsmap.get(iterate_key))}break}}const eventinfo = __dev__? { target, type, key, newvalue, oldvalue, oldtarget }: undefinedif (deps.length === 1) {if (deps[0]) {if (__dev__) {triggereffects(deps[0], eventinfo)} else {triggereffects(deps[0])}}} else {const effects: reactiveeffect[] = []// 将需要执行的副作用函数收集到 effects 数组中for (const dep of deps) {if (dep) {effects.push(...dep)}}if (__dev__) {triggereffects(createdep(effects), eventinfo)} else {triggereffects(createdep(effects))}}
}

triggereffects 函数

triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。

//triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
export function triggereffects(dep: dep | reactiveeffect[],debuggereventextrainfo?: debuggereventextrainfo
) {// spread into array for stabilization// 遍历需要执行的副作用函数集合   for (const effect of isarray(dep) ? dep : [...dep]) {// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行if (effect !== activeeffect || effect.allowrecurse) {if (__dev__ && effect.ontrigger) {effect.ontrigger(extend({ effect }, debuggereventextrainfo))}if (effect.scheduler) {// 如果一个副作用函数存在调度器,则调用该调度器effect.scheduler()} else {// 否则直接执行副作用函数effect.run()}}}
}

 ES6  Proxy 方法

const p = new Proxy(person, {        //创建代理// 查get(target,propName){console.log(`有人读取了p身上的${propName}`)return target[propName];  //反射return Reflect.get(target,propName)},// 改 增set(target, propName, value){console.log(`有人修改了p身上的${propName}属性`);target[propName] = value;},// 删deleteProperty(target, propName){console.log(`有人删除了p身上的${propName}属性`)return delete target[propName];},});

createReactiveObject

之前说到的createReactiveObject,我们接下来看看createReactiveObject发生了什么。

返回 proxy

const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
function createReactiveObject(target: unknown,toProxy: WeakMap<any, any>,toRaw: WeakMap<any, any>,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>
) {/* 判断目标对象是否被effect *//* observed 为经过 new Proxy代理的函数 */let observed = toProxy.get(target) /* { [target] : obseved  } */if (observed !== void 0) { /* 如果目标对象已经被响应式处理,那么直接返回proxy的observed对象 */return observed}if (toRaw.has(target)) { /* { [observed] : target  } */return target}/* 如果目标对象是 Set, Map, WeakMap, WeakSet 类型,那么 hander函数是 collectionHandlers 否侧目标函数是baseHandlers */const handlers = collectionTypes.has(target.constructor)? collectionHandlers: baseHandlers/* TODO: 创建响应式对象  */observed = new Proxy(target, handlers)/* target 和 observed 建立关联 */toProxy.set(target, observed)toRaw.set(observed, target)/* 返回observed对象 */return observed
}

Vue3响应式内部原理_vue3的响应式原理_monana6的博客-CSDN博客

vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客

【干货】这次终于把 Vue3 响应式原理搞懂了!_傲娇的koala的博客-CSDN博客

ES6 之 Proxy 介绍_es6 proxy_barnett_y的博客-CSDN博客

vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/113080.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PYTHON用户流失数据挖掘:建立逻辑回归、XGBOOST、随机森林、决策树、支持向量机、朴素贝叶斯和KMEANS聚类用户画像...

原文链接&#xff1a;http://tecdat.cn/?p24346 在今天产品高度同质化的品牌营销阶段&#xff0c;企业与企业之间的竞争集中地体现在对客户的争夺上&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 “用户就是上帝”促使众多的企业不惜代价去争夺尽可能多的客…

Python入门自学进阶-Web框架——40、redis、rabbitmq、git——3

git&#xff0c;一个分布式的版本管理工具。主要用处&#xff1a;版本管理、协作开发。 常见版本管理工具&#xff1a; VSS —— Visual Source Safe CVS —— Concurrent Versions System SVN —— CollabNet Subversion GIT GIT安装&#xff1a;下载安装文件&#xff1a;…

pytest pytest.ini 配置日志输出至文件

创建pytest.ini 文件 [pytest] log_file pytest_log.txt log_file_level INFO log_file_date_format %Y-%m-%d %H:%M:%S log_file_format %(asctime)s | %(filename)s | %(funcName)s | line:%(lineno)d | %(levelname)s | %(message)s import pytest import loggingdef …

Mysql中九种索引失效场景分析

表数据&#xff1a; 索引情况&#xff1a; 其中a是主键&#xff0c;对应主键索引&#xff0c;bcd三个字段组成联合索引&#xff0c;e字段为一个索引 情况一&#xff1a;不符合最左匹配原则 去掉b1的条件后就不符合最左匹配原则了&#xff0c;导致索引失效 情况二&#xff…

34、springboot切换内嵌Web服务器(Tomcat服务器)与 生成SSL证书来把项目访路径从 HTTP 配置成 HTTPS

知识点1&#xff1a;springboot切换内嵌Web服务器&#xff08;Tomcat服务器&#xff09; 知识点2&#xff1a;生成SSL证书来把项目访路径从 HTTP 配置成 HTTPS ★ Spring Boot默认的Web服务器&#xff08;Tomcat&#xff09; ▲ 基于Servlet的应用&#xff08;使用Spring MV…

【CSS】CSS 特性 ( CSS 优先级 | 优先级引入 | 选择器基本权重 )

一、CSS 优先级 1、优先级引入 定义 CSS 样式时 , 可能出现 多个 类型相同的 规则 定义在 同一个元素上 , 如果 CSS 选择器 相同 , 执行 CSS 层叠性 , 根据 就近原则 选择执行的样式 , 如 : 出现两个 div 标签选择器 , 都设置 color 文本颜色 ; <style>div {color: re…

[Linux]进程

文章目录 1. 进程控制1.1 进程概述1.1.1 并行和并发1.1.2 PCB1.1.4 进程状态1.1.5 进程命令 1.2 进程创建1.2.1 函数1.2.2 fork() 剖析 1.3 父子进程1.3.1 进程执行位置1.3.2 循环创建子进程1.3.3 终端显示问题1.3.4 进程数数 1.4 execl和execlp函数1.4.1 execl()1.4.2 execlp(…

python spyder环境配置

首先安装python&#xff0c;配置环境变量等等 其次 pip install spyder 安装 spyder 最后启动 spyder&#xff0c;cmd下 执行 spyder&#xff0c;就打开了 调试下面的代码看看是否是系统的python import sys print(sys.executable) print(sys.path) 工具-偏好-python调试器 …

Hive-启动与操作(2)

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

three.js(七):内置的二维几何体

二维几何体 PlaneGeometry 矩形平面CircleGeometry 圆形平面RingGeometry 圆环平面 PlaneGeometry 矩形平面 PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer) width — 平面沿着X轴的宽度。默认值是1。height — 平面沿着Y…

测试左移——代码审计SonarQube 平台搭建

一、sonarqube代码分析技术体系 1、代码分析工具 IDE 辅助功能 xcode、android studio阿里巴巴 java 开发手册 ide 插件支持 独立的静态分析工具 spotbugs、findbugs、androidlint、scan-build、Checkstyle、FindSecBugspmd 阿里巴巴 java 开发手册 pmd 插件 综合性的代码…

Windows系统中Apache Http服务器简单使用

1 简介 Apache HTTP服务器是一个开源的、跨平台的Web服务器软件。它由Apache软件基金会开发和维护。Apache HTTP服务器可以在多种操作系统上运行&#xff0c;如Windows、Linux、Unix等&#xff0c;并且支持多种编程语言和技术&#xff0c;如PHP、Perl、Python、Java等。…

[笔记] 阿里云域名知识

文章目录 前言一、域名二、域名常见分类2.1 泛域名2.2 为什么要设置子域名 三、记录类型3.1 A- 将域名指向一个PV4地址3.2 CNAME- 将域名指向另外一个域名3.3 AAAA- 将域名指向一个PV6地址3.4 MX- 将域名指向邮件服务器地址3.5 SRV- 记录提供特定的服务的服务器使用场景 3.6 TX…

【前端demo】将二进制数转换为十进制数 原生实现

https://github.com/florinpop17/app-ideas 总结 文章目录 效果JavaScript实现进制转换原生代码遇到的问题 效果 二进制转换为十进制若输入为空或不是二进制&#xff0c;提示清空 JavaScript实现进制转换 parseInt parseInt(111,2)手动实现 bin是输入的字符串。 functio…

【C++】关于fixed和setprecision的学习和介绍

前言 在学习swap函数的时候&#xff0c;偶然了解到了fixed和setprecision&#xff0c;这两条控制语句&#xff0c;在了解了之后&#xff0c;觉得很有用&#xff0c;于是写一篇文章来介绍fixed和setprecision这两条控制语句 fixed控制输出形式 使用fixed语句需要包含<ioma…

Matlab图像处理-图像缩放

基本概念 图像缩放是指将给定的图像在x轴方向按比例缩放a倍&#xff0c;在y轴方向按比例缩放b倍&#xff0c;从而获得一幅新的图像。 如果ab&#xff0c;即在x轴方向和y轴方向缩放的比率相同&#xff0c;则称这样的比例缩放为图像的全比例缩放。 如果a≠b&#xff0c;图像比…

在腾讯云服务器OpenCLoudOS系统中安装svn(有图详解)

1. 安装svn yum -y install subversion 安装成功&#xff1a; 2. 创建数据根目录及仓库 mkdir -p /usr/local/svn/svnrepository 创建test仓库&#xff1a; svnadmin create /usr/local/svn/test test仓库创建成功&#xff1a; 3. 修改配置test仓库 cd /usr/local/svn/te…

Visual Studio 2017安装和项目配置

目录 前言1. What、Why and How1.1 What1.2 Why1.3 How 2. 安装3. 创建新项目4. 配置OpenCV库4.1 下载opencv安装包4.2 配置系统环境变量4.3 VS项目环境配置4.4 总结 5. 已有项目添加6. Tips6.1 常用快捷键6.2 字体和颜色选择6.3 配置编译路径 结语下载链接参考 前言 最近因为项…

安装启动Stable Diffusion教程

一、下载源码 GitHub - AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI 二、安装miniconda 参考&#xff1a;安装启动yolo5教程_苍穹之跃的博客-CSDN博客 三、安装CUDA 参考&#xff1a;安装启动yolo5教程_苍穹之跃的博客-CSDN博客 四、创建虚拟环境 co…

SpringBoot入门篇3 - 整合junit、整合mybatis、基于SpringBoot实现ssm整合

目录 1.整合JUnit Spring整合JUnit SpringBoot整合JUnit 测试类注解&#xff1a;SpringBootTest 作用&#xff1a;设置JUnit加载的SpringBoot启动类 2.整合mybatis ①使用spring initializr初始化项目的时候&#xff0c;添加依赖。 ②设置数据源application.yml spring:d…