Vue3响应式原理 私

news2025/1/11 12:58:14

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

响应式的实现方式就是劫持数据,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 }
      : undefined
 
    trackeffects(dep, eventinfo)
  }
}

trackeffects 函数

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

// 收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
export function trackeffects(
  dep: dep,
  debuggereventextrainfo?: debuggereventextrainfo
) {
  let shouldtrack = false
  if (effecttrackdepth <= maxmarkerbits) {
    if (!newtracked(dep)) {
      dep.n |= trackopbit // set newly tracked
      shouldtrack = !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 tracked
    return
  }
 
  // 接着创建一个 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.set
    switch (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 changes
          deps.push(depsmap.get('length'))
        }
        break
      case 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))
          }
        }
        break
      case triggeroptypes.set:
        if (ismap(target)) {
          deps.push(depsmap.get(iterate_key))
        }
        break
    }
  }
 
  const eventinfo = __dev__
    ? { target, type, key, newvalue, oldvalue, oldtarget }
    : undefined
 
  if (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.coloradmin.cn/o/953314.html

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

相关文章

【传输层】网络基础 -- UDP协议 | TCP协议

再谈端口号端口号范围划分netstatpidof UDPUDP的特点面向数据报UDP的缓冲区 基于UDP的应用层协议 TCP认识TCP协议的报头理解封装解包理解可靠性TCP工作模式16位窗口大小6位标志位URGACKPSHRSTSYNFIN 再谈端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序 在TCP/I…

积分游戏小程序模板源码

积分游戏小程序模板源码是一款可以帮助用户快速开发小程序的工具&#xff0c;此模板源码包含五个静态页面&#xff0c;分别是首页、任务列表、大转盘、猜拳等五个页面&#xff0c;非常适合进行积分游戏等相关开发。 此模板源码的前端部分非常简单易用&#xff0c;用户可以根据…

KiCad 封装原件类型与封装焊盘不匹配 预期SMD 实际通孔

KiCad 7.0.6 PCB ERC 检查时弹出不匹配错误&#xff0c;提示&#xff1a; 封装原件类型与封装焊盘不匹配 预期SMD 实际通孔&#xff1a; 但实际的封装已经是 SMD 了呀。为啥&#xff0c;因为自己绘制的封装中属性不对。将自绘封装中的 原件类型由 通孔 改为 贴片即可&#xff1…

Python钢筋混凝土结构计算.pdf-T001-混凝土强度设计值

以下是使用Python求解上述问题的完整代码&#xff1a; # 输入参数 f_ck 35 # 混凝土的特征抗压强度&#xff08;单位&#xff1a;MPa&#xff09; f_cd 25 # 混凝土的强度设计值&#xff08;单位&#xff1a;MPa&#xff09; # 求解安全系数 gamma_c f_ck / f_cd # …

提高工作效率,轻松实现IP地址批量ping

在实际操作中&#xff0c;我们经常需要对一系列已分配的IP进行ping检测&#xff0c;以确认其是否正在运行。然而&#xff0c;我们的表格仅有一个标签页&#xff0c;且仅包含一个ip地址列。 iP192.168.196.106192.168.196.107192.168.196.108192.168.196.109 实现思路 我们的…

面对银行分支机构,UPS监控该如何应对?

UPS系统确保在电力中断或故障时&#xff0c;银行的关键系统和设备能够继续正常运行&#xff0c;从而防止因电力波动而可能导致的数据丢失和业务中断。 为了实现有效的UPS监控&#xff0c;银行需要应用监控系统。银行可以实时监测UPS系统的状态&#xff0c;及时发现潜在问题并采…

智能感测型静电中和设备由哪些部分构成

智能感测型静电中和设备是一种利用先进的传感技术和自动控制系统&#xff0c;以及适应性算法来实现静电电荷的中和和消除的设备。它主要用于消除静电带来的问题&#xff0c;比如电子元件的损坏、电磁干扰、火灾等。 智能感测型静电中和设备通常包括以下几个主要部分&#xff1…

年轻人的新社交密码:高质量小众社交app皮雀,到底怎么玩?

新一代年轻人被各种生活、工作和强社交关系充斥&#xff0c;面临着巨大的社交压力&#xff0c;因此他们在社交的选择方向上&#xff0c;逐渐远离线下社交&#xff0c;去选择线上社交&#xff0c;不同于有心理负担的线下社交&#xff0c;线上社交具有更多的选择性。基于能为年轻…

地下管线三维自动建模软件MagicPipe3D V3.0发布

2023年9月1日经纬管网建模系统MagicPipe3D V3.0正式发布&#xff0c;该版本经过众多用户应用和反馈&#xff0c;在三维地下管网建模效果、效率、适配性方面均有显著提升&#xff01;MagicPipe3D本地离线参数化构建地下管网模型&#xff08;包括管道、接头、附属设施等&#xff…

2023固态U盘、移动硬盘对比

最近测试了几款固态U盘/移动硬盘&#xff0c;希望能大家的选购有点帮助。 1、移速逸动-2T&#xff08;500MB/s&#xff09;&#xff1a;799元某音 2、爱国者u397-1T&#xff08;1000MB/s&#xff09;&#xff1a;578元京东 3、梵想FF520-512G&#xff08;500MB/s&#xff09…

直播程式源码平台细讲HTTP协议:超文本传输

HTTP协议的简介 HTTP协议是一种数据通信协议&#xff0c;是浏览器与服务器之间的协议&#xff0c;HTTP协议的中文全称为超文本传输协议&#xff0c;HTTP协议在直播程式源码平台中&#xff0c;承载着数据传输的重要任务&#xff0c;用户可以通过HTTP协议获取直播程式源码平台中提…

Docker部署RustDesk Server 设置开机自启

三、Docker安装 Docker官方和国内daocloud都提供了一键安装的脚本&#xff0c;使得Docker的安装更加便捷。 官方的一键安装方式&#xff1a; curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 国内 daocloud一键安装命令&#xff1a; curl -sSL https://…

【LeetCode】1654:到家的最少跳跃次数的解题思路 关于力扣无法return的BUG的讨论

文章目录 一、题目二、题解与代码三、神奇的BUG3.1 无法执行的 return 和 break 语句3.2 通过另一个 break 解决 一、题目 有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发&#xff0c;到达它的家。 跳蚤跳跃的规则如下&#xff1a; 它可以 往前 跳恰好 a 个位…

Android OTA 相关工具(七) 使用 lpunpack 解包 super.img

文章目录 1. lpunpack 的编译2. lpunpack 的帮助信息3. lpunpack 的用法3.1 解包所有镜像3.2 解包指定名称分区镜像3.3 解包指定槽位分区镜像 4. 其它 从 Android 10(Q) 开始&#xff0c;引入了动态分区&#xff0c;伴随的就是一组动态分区内容数据增删改查相关的操作&#xff…

数字证书UKey太多怎么管理,一分钟轻松学会

公司数字证书Ukey太多&#xff0c;管理乱&#xff0c;也不能满足异地使用需求&#xff0c;怎么办&#xff1f; 一台USB Sever就能解决&#xff01; 第一步 USB Sever有2口、8口、24口多种规格&#xff0c; 根据你的数字证书Ukey数量选择合适的规格。 第二步 把Ukey全部插上…

韶音骨传导耳机好不好,韶音骨传导耳机值得入手吗

韶音耳机的质量还是很不错的&#xff0c;其实力相比于百元价位的耳机而言领先了不少&#xff0c;具备多种功能&#xff0c;佩戴起来也是有着舒适性。它自主研发了骨传导音频技术&#xff0c;不过在今年开始&#xff0c;似乎已经将方向开始往运动偏移。 而在韶音的骨传导耳机中&…

PHY 芯片接口直连(不使用变压器)的设计

1.基础知识 网口 PHY 芯片对 TX 和 RX 信号有两种驱动方式&#xff1a;电压驱动和电流驱动。不同的驱动方式决定了 PHY 在与变压器连接的时候&#xff0c;变压器的中心抽头的接法。电压型中心抽头通过电容接到地&#xff0c;电流型中心抽头上拉至VDD&#xff0c;VDD为PHY的供电…

【问题】jdk20执行虚拟线程报错解决方案

java: ofPlatform() 是预览 API&#xff0c;默认情况下处于禁用状态。 &#xff08;请使用 --enable-preview 以启用预览 API&#xff09; 在idea增加配置 编辑配置 增加jvm参数 然后去执行 public static void main(String[] args) {int 次数 100000;System.out.println(&q…

智汇云舟亮相中国安防工程商集成商大会

智汇云舟亮相中国安防工程商集成商大会&#xff0c;以视频孪生驱动安防行业数字化转型 近日&#xff0c;由中国安全防范产品行业协会指导&#xff0c;永泰传媒主办的中国安防工程商&#xff08;系统集成商&#xff09;大会暨第69届中国安防新产品、新技术成果展示在石家庄圆满…