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.mzph.cn/bicheng/44436.shtml

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

相关文章

BEV 之 LSS概要

1、 Lift 显示估计图像下采样(16倍)后的特征点深度,将2D图像提升到3D空间,得到图像特征的视锥(点云)。 根据图像和深度均分得到3D视锥索引 下采样16倍,得到特征图大小为 H x W, 每个特征点深…

机器视觉理论入门

文章目录 前言一、马尔视觉理论二、图形与图像三、图像基础名词总结 前言 Marr的视觉计算理论立足于计算机科学,系统地概括了心理物理学、神经生理学、临床神经病理学等方面已取得的所有重要成果,是迄今为止最为系统的视觉理论。Marr 的视觉计算理论虽然…

LiteOS GPIO操作

在源码工程中&#xff0c; 华为海思在hi_io.h头文件里已定义好表示每个IO口的宏定义&#xff0c;如&#xff1a; typedef enum {HI_IO_NAME_GPIO_0, /**< GPIO0 */HI_IO_NAME_GPIO_1, /**< GPIO1 */HI_IO_NAME_GPIO_2, /**< GPIO2 */... } 并且在此头文…

【论文速读】《面向深度学习的联合消息传递与自编码器》

这篇文章来自华为的渥太华无线先进系统能力中心和无线技术实验室&#xff0c;作者中有大名鼎鼎的童文。 一、自编码架构的全局收发机面临的主要问题 文章对我比较有启发的地方&#xff0c;是提到自编码架构的全局收发机面临的主要问题&#xff1a; 问题一&#xff1a;基于随…

洛杉矶裸机云大宽带服务器的特性和优势

洛杉矶裸机云大宽带服务器是结合了物理服务器性能和云服务灵活性的高性能计算服务&#xff0c;为用户提供高效、安全的计算和存储能力。在了解如何使用洛杉矶裸机云大宽带服务器之前&#xff0c;需要了解其基本特性和优势。以下是对洛杉矶裸机云大宽带服务器的具体分析&#xf…

使用lv虚拟卷扩展磁盘

使用centos演示。 首先创建centos虚拟机。链接&#xff1a;VMWARE安装Centos8,并且使用ssh连接虚拟机-CSDN博客 1. 增加磁盘。 选中要扩容的虚拟机&#xff0c;右键选择设置&#xff0c;然后点击磁盘&#xff0c;选择添加。 这里选择NVM的磁盘。选择这种磁盘是为了保持与之前…

有Daemon字眼的守护线程,和没有的差异是什么?怎么创建有daemon字眼的线程?

在编程中&#xff0c;尤其是在Java等支持多线程的编程语言中&#xff0c;守护线程&#xff08;Daemon Thread&#xff09;与普通线程&#xff08;Non-Daemon Thread&#xff09;之间存在一些关键差异。这些差异主要体现在它们对程序终止行为的影响上。 主要差异 程序终止行为…

SpringBoot新手快速入门系列教程:前述

我自己是一个SpringBoot新手&#xff0c;花了一天时间学了SpringBoot。大家不要惊讶&#xff0c;前提是我自己已经有了10几年的编程经验精通多门语言&#xff0c;并且在人间最强兵器Chat某T的AI助手帮助下&#xff0c;才能创造一天快速学会一个框架的神话。 当然中间遇到了很多…

笔试算法刷题

猿辅导2021校园招聘笔试&#xff08;算法一&#xff09; 牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推&#xff0c;求职就业一站解决_牛客网 (nowcoder.com) 第一眼看到这个题想到的是蓝桥杯飞机降落&#xff0c;贪心题。但是这样算的是最大不相交区间数量&#xff0…

Test-Time Adaptation via Conjugate Pseudo-labels--论文笔记

论文笔记 资料 1.代码地址 https://github.com/locuslab/tta_conjugate 2.论文地址 https://arxiv.org/abs/2207.09640 3.数据集地址 论文摘要的翻译 测试时间适应(TTA)指的是使神经网络适应分布变化&#xff0c;在测试时间仅访问来自新领域的未标记测试样本。以前的TT…

致远漏洞(登陆绕过+任意文件上传)

漏洞复现 1.获得cookie POST /seeyon/thirdpartyController.do HTTP/1.1 Host: 192.168.1.9 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 Accept-Langua…

大数据面试题之ElasticSearch(1)

目录 ElasticSearch介绍 ElasticSearch架构 ElasticSearch深度分页 ElasticSearch调优手段有哪些 ElasticSearch脑裂了怎么办 ElasticSearch的倒排索引 ElasticSearch如何实现master选举 ElasticSearch的搜索过程说下 ElasticSearch如何在并发情况下保证读写一 Elast…

uni-app三部曲之一: Pinia使用

1.引言 最近在学习移动端的开发&#xff0c;使用uni-app前端应用框架&#xff0c;通过学习B站的视频以及找了一个开发模板&#xff0c;终于是有了一些心得体会。 B站视频1&#xff1a;Day1-01-uni-app小兔鲜儿导学视频_哔哩哔哩_bilibili B站视频2&#xff1a;01-课程和uni的…

简述设计模式-策略模式

概述 在策略模式中一个类的行为或者算法可以在运行时更改&#xff0c;这种类型的设计模式属于行为型模式。 在策略模式中定义了一系列的算法和策略&#xff0c;并将每个算法封装在独立的类中&#xff0c;使得他们能够互相替换&#xff0c;通过使用策略模式可以在运行时选择不…

java 实现Comparable接口和实现Comparator接口排序的区别

Comparable接口 作用&#xff1a; Comparable接口是在类的内部实现的&#xff0c;用于指定类的默认比较规则。当一个类实现了Comparable接口时&#xff0c;它必须实现compareTo方法&#xff0c;该方法用于定义对象之间的自然顺序。 实现方式&#xff1a; 实现Comparable接口的…

洛谷P10716【MX-X1-T4】「KDOI-05」简单的字符串问题(扩展kmp+set+二分+扫描线树状数组)

题目 思路来源 小羊肖恩 题解 羊神这个做法tql&#xff0c;当时只是机械地写&#xff0c;过了之后再想想&#xff0c;才觉得确实是nb 先扩展kmp&#xff08;Z函数&#xff09;预处理出来数组&#xff0c;记z[i]为i往后可以和前缀匹配的最大长度 对于每个询问(p,cnt)&#x…

隧道转发:保护你的数据传输安全

你曾经是否担心过你的数据安全&#xff1f;现代网络威胁不断增加&#xff0c;保护数据传输的安全性变得愈发重要。今天为大家介绍一种强大的网络传输技术——隧道转发&#xff08;Tunneling&#xff09;。这项技术不仅能有效保障数据安全&#xff0c;还能保护用户隐私&#xff…

centOS79中安装nginx12.15

##red## &#x1f534; 大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff0c;雄雄的小课堂。 前言 装了这么多&#xff0c;发现Nginx是最简单的&#xff0c;一次性就搞定了。下面我们来看看如何安装 安装Nginx 安装gcc-c编译器 分开运行&#xff1a; yum…

anaconda安装pytorch

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️如遇文章付费&#xff0c;可先看…

python爬虫入门(三)之HTML网页结构

一、什么是HTML 1、网页的三大技术要素&#xff1a; HTML定义网页的结构和信息&#xff08;骨架血肉&#xff09;CSS定义网页的样式&#xff08;衣服&#xff09;JavaScript定义用户和网页的交互逻辑&#xff08;动作&#xff09; 2、一个最简单的HTML&#xff1a;用<>…