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.mzph.cn/news/60354.shtml

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

相关文章

版本控制 Git工具的使用

版本控制的概念&#xff1a; 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。简单来说就是用于管理多人协同开发…

C语言每日一练------(Day3)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今天练习题的关键字&#xff1a; 尼科彻斯定理 等差数列 &#x1f493;博主csdn个人主页&#xff1a…

2023_Spark_实验一:Windows中基础环境安装

Ⅰ、WINDOWS中安装JDK1.8 一、下载安装包 链接&#xff1a;百度网盘 请输入提取码 所在文件夹&#xff1a;根目录或者大数据必备工具--》开发工具(前端后端)--》后端 下载文件名称&#xff1a;jdk-8u191-windows-x64.exe 二、安装JDK 1.现在转到下载的exe文件可用的文件夹&…

【Vue3+Ts】项目启动准备和配置项目代码规范和css样式的重置

项目启动准备 创建项目&#xff08; 使用Vite 构建工具创建项目模板&#xff09;目录介绍插件安装创建别名编译说明项目配置配置icon和标题配置项目别名配置ts.config.json检测vscode的插件是否配置 配置项目代码规范集成editorconfig配置prettier工具库ESLint检测配置 CSS样式…

transformer实现词性标注

1、self-attention 1.1、self-attention结构图 上图是 Self-Attention 的结构&#xff0c;在计算的时候需要用到矩阵 Q(查询), K(键值), V(值)。在实际中&#xff0c;Self-Attention 接收的是输入(单词的表示向量 x组成的矩阵 X) 或者上一个 Encoder block 的输出。而 Q, K, V…

Hive-启动与操作(2)

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

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

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

堆,堆排序和TOP—K问题(C语言版)

前言 堆是一种重要的数据结构&#xff0c;堆分为大根堆和小根堆&#xff0c;大根堆堆顶的数据是最大的&#xff0c;小根堆堆顶的数据是最小的&#xff0c;堆在逻辑结构上是一颗完全二叉树&#xff0c;这棵树中如果满足根节点大于左右子树&#xff0c;每个节点都满足这个条件就是…

Rabbitmq的Shovel

Federation 具备的数据转发功能类似&#xff0c; Shovel 够可靠、持续地从一个 Broker 中的队列 ( 作为源端&#xff0c;即source)拉取数据并转发至另一个 Broker 中的交换器 ( 作为目的端&#xff0c;即 destination) 。作为源端的队列和作为目的端的交换器可以同时位于…

微服务dubbo和nexus

微服务是一种软件开发架构风格&#xff0c;它将一个应用程序拆分成一组小型、独立的服务&#xff0c;每个服务都可以独立部署、管理和扩展。每个服务都可以通过轻量级的通信机制&#xff08;通常是 HTTP/REST 或消息队列&#xff09;相互通信。微服务架构追求高内聚、低耦合&am…

Linux--VMware的安装和Centos

一、VMware和Linux的关系 二、VMware的安装 VM_ware桌面虚拟机 最新中文版 软件下载 (weizhen66.cn) VMware-Workstation-Lite-16.2.2-19200509-精简安装注册版.7z - 蓝奏云 如果安装不成功&#xff0c;则设置BIOS 三、在VMware中加入Centos 下载地址&#xff1a; CentOS-…

Linux 忘记密码解决方法

很多朋友经常会忘记Linux系统的root密码&#xff0c;linux系统忘记root密码的情况该怎么办呢&#xff1f;重新安装系统吗&#xff1f;答案是不需要进入单用户模式更改一下root密码即可。 步骤如下&#xff1a; 重启linux系统 3 秒之内要按一下回车&#xff0c;出现如下界面 …

unity-AI自动导航

unity-AI自动导航 给人物导航 一.地形创建 1.首先我们在Hierarchy面板中创建一个地形对象terrian&#xff0c;自行设定地形外貌&#xff0c;此时我们设置一个如下的地形外观。 二.创建导航系统 1.在主人公的Inspector、面板中添加Nav Mesh Agent &#xff08;导航网格代理&…

Linux操作系统--linux概述

1.Linux概述 Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff08;OS&#xff09;。简单的说就是一种操作系统。在日常中常见的操作系统有一下三种: 2.linux起源和背景 (1).linux的诞生 linux操作系统是由李纳斯托瓦兹&#xf…

C++ 多重继承

所谓多重继承就是一个儿子有好几个爹&#xff0c;然后一个人继承了这几个爹的财产。只需注意构造顺序即可&#xff0c;反正析构的顺序也是一样的。 #include <iostream> #include <string.h> using namespace std;class base_a { public:base_a(const char *str){…

二十、观察者模式

一、什么是观察者模式 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&…

2007-2022年上市公司污染排放数据/2007-2022年上市公司污染排放水平、污染排放量数据

2007-2022年上市公司污染排放数据/2007-2022年上市公司污染排放水平、污染排放量数据 1、时间&#xff1a;2007-2022年 2、指标&#xff1a;证券代码、year、化学需氧量、氨氮排放量、总氮、总磷、水体综合污染当量对数、二氧化硫、氮氧化物、烟尘、空气综合污染当量对数、总…

静态类方法的同步

由于在调用静态方法时&#xff0c;对象实例不一定被创建。因此&#xff0c;就不能使用this来同步静态方法&#xff0c;而必须使用Class对象来同步静态方法。代码如下&#xff1a; 通过synchronized块同步静态方法 public class StaticSyncBlock { public static void…

word表格左侧边线明明有,但却不显示

如题&#xff0c;解决方法&#xff1a; 方案一&#xff1a; 1&#xff09;选中表格 2&#xff09;布局菜单--->自动调整 3&#xff09;自动调整中&#xff0c;选择“根据窗口自动调整表格” 4&#xff09;表格左侧边线就显示出来了。 方案二&#xff1a;把表格粘贴到新…

Kubernetes技术--Kubernetes架构组件以及核心概念

1.Kubernetes集群架构组件 搭建一个Kubernetes环境集群,其架构如下所示: 内容详解: Master:控制节点,指派任务、决策 Node:工作节点,实际干活的。 Master组件内容: