Vue3.js“非原始值”响应式实现基本原理笔记(三)

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:前端小王hs

阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来

书籍:《Vue.js设计与实现》 作者:霍春阳

本篇博文将在书第5.1节5.4节的基础上进一步总结所提到的基础概念,附加了测试的代码运行示例,方便正在学习Vue3想分析Vue3源码的朋友快速阅读

如有帮助,不胜荣幸

前文:
Vue3.js“非原始值”响应式实现基本原理笔记(一)
Vue3.js“非原始值”响应式实现基本原理笔记(二)

合理触发响应

5.4节讨论了除上一节笔记中其他的合理操作

值没有发生变化

如果执行p.foo=1,那么同样不应该触发副作用函数,所以需要在set中新增一个判断,即旧值和新值是否相等,代码如下:

const p = new Proxy(obj, {
  set(target, key, newVal, receiver) {
    // 获取旧值
    const oldVal = target[key]
    const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
    const res = Reflect.set(target, key, newVal, receiver)
    // 比较新值与旧值,只要当不全等的时候才触发响应
    if (oldVal !== newVal) {
      trigger(target, key, type)
    }
    return res
  },  
})

这时还需考虑一个额外情况——NaN的全等对比

  1. NaN === NaN // false
  2. NaN !== NaN // true

所以还需再加一个判断条件,代码如下:

 if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal ))

从原型上继承属性——reactive

在书中的案例中,作者定义了一个reactive函数,对Proxy进行了封装,代码如下:

function reactive(obj) {
  return new Proxy(obj, {
    // 其他逻辑
  })
}

这其实是第一次出现reactive,值得注意

接着书中给了一个案例代码:

const obj = {}
const proto = { bar: 1 }
const child = reactive(obj)
const parent = reactive(proto)
// 使用 parent 作为 child 的原型
Object.setPrototypeOf(child, parent)

effect(() => {
  console.log(child.bar) // 1
})
// 修改 child.bar 的值
child.bar = 2 // 会导致副作用函数重新执行两次

分析一下各个对象的关系

  1. childobj的代理对象
  2. parentproto的代理对象
  3. Object.setPrototypeOf设置parentchild的原型

我们知道原型链,如果访问child.bar没有,那么会沿着原型链找到parent.bar,也可以说是继承

现在来分析一下child.bar会导致副作用函数执行两次的流程:

  1. 读取child.bar,触发child里的get
  2. 执行Reflect.get(obj, 'bar', receiver)
  3. 发现child内没有bar,读取parent.bar
  4. 读取parent.bar时与副作用函数建立联系
  5. 在第一次读取的track(obj,bar)中,child.bar也与副作用函数建立了联系

所以child.barparent.bar都与副作用函数建立了联系

修改child.bar时的流程:

  1. 调用child.set,执行Reflect.set
  2. child没有bar,变为执行parent.set

我们知道执行set就会触发trigger,所以就执行了两次副作用函数

那如何避免两次执行呢?答案是在set中进行区分两次更新

首先看一下child里的set,代码如下:

// child 的 set 拦截函数
set(target, key, value, receiver) {
  // target 是原始对象 obj
  // receiver 是代理对象 child
}

这里的receiver,一般情况就是Proxy的实例本身,在这里也就是child

可以看阮一峰ES6里的示例代码:

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    return true;
  }
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
myObj.foo === myObj // true

接着看一下parent里的set,代码如下:

// parent 的 set 拦截函数
set(target, key, value, receiver) {
  // target 是原始对象 proto
  // receiver 仍然是代理对象 child
}

这里我们知道target就是parent被代理对象proto,但为什么receiver还是child呢?

这是因为实际操作的上下文对象child,而不是parent

那么现在可以发现一个特点,就是target是变化的,而receiver是不变的,所以可以通过判断receiver是否是target的代理对象即可

也就是obj的代理对象是child,而proto的代理对象不是child

回到问题本身的解决方案,首先是在get中添加了一个判断条件,代码如下:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 代理对象可以通过 raw 属性访问原始数据
      if (key === 'raw') {
        return target
      }

      track(target, key)
      return Reflect.get(target, key, receiver)
    }
    // 省略其他拦截函数
  })  
}

如果访问raw,那么会返回对应的值,例如child.raw,那么返回obj

接着回到set,新增一个语句,代码如下:

function reactive(obj) {
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      const oldVal = target[key]
      const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      const res = Reflect.set(target, key, newVal, receiver)

      // target === receiver.raw 说明 receiver 就是 target 的代理对象
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          trigger(target, key, type)
        }
      }

      return res
    }
    // 省略其他拦截函数
  })  
}

set中判断了target是否等于receiver.raw,如果是的话才会调用trigger

这样的话问题就解决了

总结

  1. 使用reactive封装Proxy,为浅响应和深响应做铺垫
  2. 解决了原型链修改属性问题

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ESP32CAM物联网教学02

ESP32CAM物联网教学02 物联网门锁 小智来到姑姑家门口,按了门铃;还在公司上班的姑姑用电脑给小智开了门,让他先进屋休息。小智对物联网门锁产生了兴趣:什么是物联网?为什么这么厉害? 初识物联网 我们在百…

【论文阅读笔记】Meta 3D AssetGen

【论文阅读笔记】Meta 3D AssetGen: Text-to-Mesh Generation with High-Quality Geometry, Texture, and PBR Materials Info摘要引言创新点 相关工作T23D基于图片的3d 重建使用 PBR 材料的 3D 建模。 方法文本到图像:从文本中生成阴影和反照率图像Image-to-3D:基于pbr的大型重…

python 比webdriver更好用的ChromiumPage

优点(目前发现的): 不用配合selenium不用下载对应浏览器的webdriver,不用对应浏览器版本不用设置webdriver路径之类的设置目前没看到有出现像webdriver类似的浏览器被控制的提示,使用过程中好像也没被检测出来。每次不…

unity3d:Shader知识点,矩阵,函数,坐标转换,Tags,半透明,阴影,深度,亮度,优化

基本结构 Shader "MyShaderName" {Properties {// 属性}SubShader {// 针对显卡A的SubShaderPass {// 设置渲染状态和标签Tags { "LightMode""ForwardBase" }// 开始Cg代码片段CGPROGRAM// 该代码片段的编译指令,例如:#p…

【vite创建项目】

搭建vue3tsvitepinia框架 一、安装vite并创建项目1、用vite构建项目2、配置vite3、找不到模块 “path“ 或其相对应的类型声明。 二、安装element-plus1、安装element-plus2、引入框架 三、安装sass sass-loader1、安装sass 四、安装vue-router-next 路由1、安装vue-router42搭…

python基础篇(8):异常处理

在Python编程中,异常是程序运行时发生的错误,它会中断程序的正常执行流程。异常处理机制使得程序能够捕获这些错误,并进行适当的处理,从而避免程序崩溃。 1 错误类型 代码的错误一般会有语法错误和异常错误两种,语法错…

CTF常用sql注入(一)联合注入和宽字节

0x01 前言 给自己总结一下sql注入的常用姿势吧,记录一下学习 0x02 联合 联合注入的关键词是union SQL的union联合注入原理是联合两个表进行注入攻击,使用union select关键词来进行联合查询。 那么为什么我们在题目中一般是只写一个呢 因为 $sql &quo…

逆变器学习笔记(三)

DCDC电源芯片外围器件选型_dcdc的comp补偿-CSDN博客、 1.芯片的COMP引脚通常用于补偿网络: 芯片的COMP引脚通常用于补偿网络,在控制环路中发挥重要作用。COMP引脚接电容和电阻串联接地,主要是为了稳定控制环路、调整环路响应速度和滤波噪声…

cs231n作业1——SVM

参考文章:cs231n assignment1——SVM SVM 训练阶段,我们的目的是为了得到合适的 𝑊 和 𝑏 ,为实现这一目的,我们需要引进损失函数,然后再通过梯度下降来训练模型。 def svm_loss_naive(W, …

NAT 打洞

由于 ipv4 地址数量的有限性,导致实际网络部署模式中存在大量的 NAT 网络。对于 NAT 内部的主机,可以主动发起去公网的流量,但对于位于不同 NAT 内的两台主机而言,想要直接进行点对点的连接,就需要用到打洞技术了。 常…

Bash ——shell

Bash作为用户与操作系统之间的接口,让用户通过命令行输入各种指令来控制和操作计算机系统。 shell的两种解释: 1.linux命令解释器 Terminal 终端 ——》shell命令 ——》 Linux kernel (内核) Linux内核的作用: 1.…

AI与编程:一个学生的心路历程与思考

前言 大家好,本人是在一个在校的大学生,方向是前端语言。爱好是码代码和看一点小新闻,游戏也是喜爱的。其实本篇文章的想法是源于网上一些人对AI以及对前端的看法,看完网上的评论后我也是有感而发。本篇文章的讨论中心也是围绕着A…

IDA*——AcWing 180. 排书

IDA* 定义 IDA*(Iterative Deepening A*)是一种结合了深度优先搜索(DFS)的递归深度限制特性和A搜索的启发式估价函数的搜索算法。它主要用于解决启发式搜索问题,尤其是当搜索空间很大或者搜索成本不确定时。 IDA* 是…

SprongBoot及其基础应用全套部署脚本和配置

POM.xml配置 </dependencies> <!--skywalking日志监控依赖--><dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-logback-1.x</artifactId><version>8.5.0</version></dependency&g…

轻松驾驭开发之旅:Maven配置阿里云CodeUp远程私有仓库全攻略

文章目录 引言一、为什么选择阿里云CodeUp作为远程私有仓库&#xff1f;二、Maven配置阿里云CodeUp远程私有仓库的步骤准备工作配置Maven的settings.xml文件配置项目的pom.xml文件验证配置是否成功 三、使用阿里云CodeUp远程私有仓库的注意事项 引言 在软件开发的世界里&#…

软件工程(上)

目录 软件过程模型&#xff08;软件开发模型&#xff09; 瀑布模型 原型模型 V模型 构件组装模型 螺旋模型&#xff08;原型瀑布&#xff09; 基于构件的软件工程&#xff08;CBSE&#xff09; 快速应用开发模型&#xff08;RAD&#xff09; 统一过程&#xff08;UP&a…

Http Json参数到x-www-form-urlencoded参数的在线转换工具

Json参数到x-www-form-urlencoded参数的在线转换工具

C语言 printf 函数多种输出格式以及占位输出

一、输出格式 在C语言中&#xff0c;printf 函数提供了多种输出格式&#xff0c;用于控制不同类型数据的输出方式。 1.整数输出格式 %d&#xff1a;以十进制形式输出整数。 %o&#xff1a;以八进制形式输出整数&#xff08;无前导0&#xff09;。 %x 或 %X&#xff1a;以十六进…

CMD命令详细介绍 | 超详细版本!

文章目录 启动cmd命令用户启动使用管理员的账号启动 文件夹命令网络命令其他常用命令介绍常用快捷方式程序员相关命令 本文参考了博客园一篇帖子&#xff0c;ULR&#xff1a;cmd常用命令介绍(可收藏) - Mrwhite86 - 博客园 (cnblogs.com) CMD是Windows操作系统自带的命令行解释…

嵌入式C语言面试相关知识——内存管理(不定期更新)

嵌入式C语言面试相关知识——内存管理&#xff08;不定期更新&#xff09; 一、博客声明二、自问题目1、嵌入式系统的内存布局是怎么样的&#xff1f;2、动态内存分配在嵌入式系统中的使用有什么注意事项&#xff1f;3、什么是内存碎片&#xff0c;如何减少内存碎片&#xff1f…