Vue3中的响应式对象Reactive源码分析

ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述

本文中所有的源代码均为 JS简易版本,便于阅读理解

Vue3源码分析之 Ref 与 ReactiveEffect

Reactive流程图

  1. reactive(obj) 都做了些什么?

    reactive函数其实只做了 过滤只读对象 的功能,创建 Proxy 代理是通过调用 createReactiveObject 函数来进行的

    reactive 函数源代码

    // WeakMap 可以有效避免内存泄漏问题,因为他的键是弱引用的
    // const reactiveMap = new WeakMap(): 已收集的依赖的缓存列表(属于:effect模块)
    // const { mutableHandlers } = require('./baseHandlers模块') function reactive(target) {
    // 如果这个对象被标记为只读,那么我们只需要将他返回即可(因为只读不存在修改)
    if(target && target[ReactiveFlags.IS_READONLY]) {
    return target
    }
    return createReactiveObject(
    target,
    mutableHandlers,
    reactiveMap
    )
    }

    createReactiveObject 函数返回一个 响应式代理(Proxy), 针对 目标对象(target) 进行过滤和特殊处理

    createReactiveObject 函数源代码

    /**
    * 创建 reactive 响应式代理
    * @param {*} target 代理对象
    * @param {*} baseHandlers Array Object
    * @param {*} proxyMap 依赖 缓存集合
    * 下方特殊类型暂时不实现,不影响核心逻辑
    * collectionHandlers:主要是处理:Map、Set、WeakMap、WeakSet 类型
    * isReadonly: 是否只读
    * @returns Proxy
    */
    function createReactiveObject(target, baseHandlers, proxyMap /* collectionHandlers, isReadonly */) {
    // 如果不是一个对象(这里没有在 reactive 中做处理,是由于 readonly 类似的api也需要调用该函数)
    if(!isObject(target)) {
    return target
    } // 如果已经收集,将缓存列表中的内容返回即可
    const existingProxy = proxyMap.get(target)
    if(existingProxy) {
    return existingProxy
    } // 创建代理对象(目前仅处理 Array、Object)
    const proxy = new Proxy(target, baseHandlers) // collectionHandlers) // 收集已经代理的对象
    proxyMap.set(target, proxy)
    // 将响应式对象 Proxy 返回
    return proxy
    }
  2. baseHandlers 模块中是如何创建 get 和 set 等方法的?

    mutableHandlers 对象中包含 get, set, deleteProperty, has, ownKeys 几种操作方法

    /**
    * 以下方法本文不进行实现:
    * deleteProperty: 删除属性时*
    * has:检查目标对象是否存在此某个属性(因为 写入/删除 对象属性会受到影响,需要收集)
    * ownKeys:获取keys数组列表(因为如果 写入/删除 对象中的属性,keys数组长度会变化,需要收集)
    */

    get = createGetter()实现代码

    const get = createGetter()
    
    function createGetter(isReadonly =false) {
    return function get(target, key, receiver) {
    // console.log('get: ', key)
    // const targetIsArray = Array.isArray(target) // 源码中数组的特殊处理,主要是针对数组的一些原生方法进行处理(源码中对应的函数:createArrayInstrumentations())
    // 处理:'push', 'pop', 'shift', 'unshift', 'splice'(避免数组长度被追踪,某些情况可能造成无限循环)
    // 处理:'includes', 'indexOf', 'lastIndexOf'(对可能产生依赖作用的方法进行追踪)
    // if(!isReadonly && targetIsArray && Object.prototype.hasOwnProperty(arrayInstrumentations, key)) {
    // } const res = Reflect.get(target, key, receiver)
    // 如果是只读的,那么不需要进行收集,因为无法set,就不会被更改
    if(!isReadonly) {
    // 若当前存在 activeEffect(活跃的 effect),那么需要对其进行收集
    track(target, key)
    } if(isObject(res)) {
    // 如果结果是一个 Object,比如:const obj = { a: 1, b: { a: '2-1' } },获取 obj.b.a
    // 如果不将 b 进行响应式代理,那么在读取 a 的时候无法触发 get 方法,因为 proxy 只会作用第一层对象
    // 源码中有一个 shallow 参数来判读是否 执行嵌套对象的深度转换
    return isReadonly ? readonly(res) : reactive(res)
    } return res
    }
    }

    createSetter函数核心逻辑其实就是 执行 Reflect.set 方法,触发trigger

    set = createSetter()实现代码

    const set = createSetter()
    // shallow:浅监听
    function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
    // 要在 trigger 函数执行之前更改值,否则拿不到最新的值
    const result = Reflect.set(target, key, value, receiver) trigger(target, key)
    // trigger函数执行完成后,返回结果
    return result
    }
    }

    baseHandlers 模块会导出一个 mutableHandlers 对象

    export const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
    }
  3. effect 模块的拓展

    effect模块针对 reactive,主要增加了 track(收集依赖)、trigger(触发依赖) 两个方法及 targetMap(缓存 effec) 属性

    effct.js

    effect模块源码在讲解 Ref 的随笔中已进行分析(Vue3源码分析之 Ref 与 ReactiveEffect

    let activeEffect // 记录当前活跃的对象
    let shouldTrack = false // 标记是否追踪 const isTracking = () => activeEffect && shouldTrack // 工具函数 // 新增:存储已经收集的依赖(reactive)
    const targetMap = new WeakMap()
    // ...... 其他

    track 源码

    // 依赖收集
    function track(target, key /** trackType */) {
    if(!isTracking()) {
    return
    } // 从缓存列表中尝试获取
    let depsMap = targetMap.get(target)
    if(!depsMap) {
    // 如果没有被收集过,那么进行收集, 那么创建一个 depsMap
    targetMap.set(target, (depsMap = new Map()))
    } // 尝试从已收集的依赖中获取 effects
    let dep = depsMap.get(key)
    if(!dep) {
    // 如果这个这个 key 不存在,则创建一个 dep
    depsMap.set(key, (dep = new Set()))
    } // 进行 effect 收集,dep.add(activeEffect)
    trackEffects(dep)
    }

    trigger 源码

    /**
    * type, oldValue, newValue, oldTarget:这几个参数在源码中,主要是为了 dev 环境中的日志提示作用
    */
    function trigger(target, key /* type, oldValue, newValue, oldTarget */) {
    const depsMap = targetMap.get(target)
    // depsMap可能为空
    if (!depsMap) {
    return
    } const deps = depsMap.get(key)
    // 将 set对象 deps 转为数组
    triggerEffects([...deps])
    }

    测试代码

    const testObj = reactive({ a: 1 })
    
    // 模拟一个 computed
    const testComputed = () => {
    // 创建一个 effect
    const testEffect = new ReactiveEffect(() => {
    // 在回调函数中打印 testObj.a
    return console.log('-- textComputed --', testObj.a)
    }) // 此时,targetMap.get(target) = dep<Set>[testEffect<ReactiveEffect>]
    // testEffect.deps = [dep<Set>[testEffect<ReactiveEffect>]]
    testEffect.run() console.log('testEffect.deps', testEffect.deps)
    } testComputed() testObj.a += 1 // console -> -- textComputed -- 2

备注:手写的 JS 简易版本源码,方便阅读理解很多边缘情况并没有完全考虑,问题请留言

Vue3中的响应式对象Reactive源码分析的更多相关文章

  1. bootstrap_栅格系统_响应式工具_源码分析

    -----------------------------------------------------------------------------margin 为负 ​使盒子重叠 ​等高 等高 ...

  2. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  3. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  4. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  5. jQuery Deferred对象详细源码分析(-)

    本系列文章讲介绍这个Deferred东西到底拿来干什么,从1.5版本加进来,jQuery的很多代码都重写了.直接先上源码分析了,清楚了源码分析,下节将讲具体的应用 以及应用场景. 创建对象 var d ...

  6. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  7. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  8. RocketMQ中PullConsumer的消息拉取源码分析

    在PullConsumer中,有关消息的拉取RocketMQ提供了很多API,但总的来说分为两种,同步消息拉取和异步消息拉取 同步消息拉取以同步方式拉取消息都是通过DefaultMQPullConsu ...

  9. 2、JDK8中的HashMap实现原理及源码分析

    本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和Linked ...

随机推荐

  1. 【LeetCode】222. Count Complete Tree Nodes 解题报告(Python)

    [LeetCode]222. Count Complete Tree Nodes 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个 ...

  2. 【LeetCode】678. Valid Parenthesis String 解题报告(Python)

    [LeetCode]678. Valid Parenthesis String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人 ...

  3. python学习第六天:python基础(条件判断、循环)

    条件判断 格式 if <条件判断1>: <执行1> elif <条件判断2>: <执行2> elif <条件判断3>: <执行3> ...

  4. 一个网关服务性能问题的Dump分析

    本篇文章分为三部分,首先简单介绍一下分析的工具Windbg,其次针对一个网关服务性能问题进行逐步刨析,最后针对性能问题的分析总结. 一 Windbg介绍 1.Windbg是个非常强大的调试器,它设计了 ...

  5. CS5266代替AG9311|Type C转HDMI带PD3.0转换芯片|AG9311替代方案

    ALGOLTEK AG9311是一款带PD3.0 Type C转HDMI的转换芯片,它主要用于usb Type-c拓展坞以及多功能usb Type-c转换器等产品设计当中,台湾瑞奇达新推出的CS526 ...

  6. 【MySQL作业】SELECT 数据查询——美和易思MySQL运算符应用习题

    点击打开所使用到的数据库>>> 1.查询指定姓名的客户(如"张晓静")的地址和电话号码. select address 地址, phone 电话号码 from c ...

  7. 解决vite+elementplus 打包后出现的下拉列表多出空元素的bug

    打包后下拉列表出现的空元素bug 之前的项目element-plus版本是 "^1.0.2-beta.70"  把他升级一下就好了 npm i element-plus@1.0.2 ...

  8. 线性基(Linear Basis)学习笔记

    前言 我看网络上没有什么非常系统的教学,可能是我太菜了吧,现在才学,做个记录给自己看. 简略介绍 一个数集能两两异或,能表出许多新的数. 线性基是一个集合,能够在记录最少的数的基础上,表示出一个等价的 ...

  9. 基于appnium+python+夜神模拟器的自动化

    首先搭好appnium环境!参考https://www.cnblogs.com/testlearn/p/11419797.html 1.安装夜神模拟器 下载安装夜神模拟器后,在cmd命令输入adb c ...

  10. POJ2891Strange Way to Express Integers

    http://poj.org/problem?id=2891 实际上就是一个一元线性同余方程组.按照合并的方式来解即可. 有一个注意点,调用函数是会慢的. #include<iostream&g ...