vue3 reactive原理(二)-代理Set和Map及ref原理
Set和Map类型的数据也属于异质对象,它们有特定的属性和方法用来操作自身。因此创建代理时,针对特殊的方法需要特殊的对待。
Vue 的ref 是基于reactive函数实现的,它在其基础上,增加了基本类型的响应性、解决reactive在解构时丢失响应性的问题及在模版字面量中自动脱落Ref.
1 代理Set和Map
function createReactive(obj,isShallow = false) {return new Proxy(obj, {get(target, p, receiver) {track(target,p)const value = Reflect.get(target,p,receiver)if (isShallow) return valuereturn typeof value === 'object' && value !== null ? reactive(value) : value},// 省略其他代码})
}const proxyObj = reactive(new Set())
// 报错 Method get Set.prototype.size called on incompatible receiver
console.log(proxyObj.size)
上面代码报错:receiver上不兼容size方法。这是因为上面代码中,receiver 指向Proxy的代理对象,它是没有size方法的。下面是对get方法改进。
get(target, p, receiver) {if (p === 'size') return Reflect.get(target,p,target)// 省略其他代码
},
但是,改进后再执行proxyObj.add(1),又报错:receiver 上不兼容add方法。因为是proxyObj执行add函数,add函数里的this始终指向proxyObj。这是可以使用函数的bind方法,来绑定函数中this的值。
get(target, p, receiver) {// 省略其他代码const value = Reflect.get(target,p,receiver)if (typeof value === 'function') return value.bind(target)if (isShallow) return valuereturn typeof value === 'object' && value !== null ? reactive(value) : value
},
1.1 建立响应联系
- size属性是一个只读属性,Set的add、delete会改变它的值。
- 调用Set的add方法时,如果元素已存在于Set中,就不需要触发响应。调用Set的delete方法时,如果元素不存在于Set中,也不需要触发响应。
const mutableInstrumentation = {add(key) {const target = this.rawconst hadKey = target.has(key)const res = target.add(key)if (!hadKey && res) {trigger(target, key, 'ADD')}return res},delete(key) {const target = this.rawconst hadKey = target.has(key)const res = target.delete(key)if (hadKey && res) {trigger(target, key,'DELETE')}return res}
}
// Proxy 中的get代理
get(target, p, receiver) {// 省略其他代码track(target,p)if (mutableInstrumentation.hasOwnProperty(p)) {return mutableInstrumentation[p]}// 省略其他代码
},
1.2 避免污染原始数据
const proxySet = reactive(new Set())
const map = new Map()
const proxyMap = reactive(map)
proxyMap.set('set',proxySet)
effect(() => {console.log(map.get('set').size)
})
console.log("----------------")
proxySet.add(1) // 触发响应
上面原始数据具有了响应性,这不符合需求(原始数据应不具备响应性)。产生这个的原因是,在设置值时,直接把响应体对象也添加进原始对象了。所以,解决的关键在于:设置值时,如果该对象是响应体对象,则取其目标对象。
// mutableInstrumentation 对象的set方法
set(key,value) {const target = this.rawconst had = target.has(key)const oldValue = target.get(key)target.set(key,value.raw || value) // 去目标对象if (!had) {track(target, 'key', 'ADD')} else if (oldValue !== value && (oldValue === oldValue || value === value)) {trigger(target,key,'SET')}
}
1.3 处理forEach
1)forEach 只与键值对的数量有关,所以当forEach被调用时,让ITERATE_KEY与副作用函数建立联系。
2)当set方法设置的属性存在时,但属性值不同时,也应该触发forEach。
3)forEach函数的参数callback,它是有原始对象调用的,这意味着callback函数中的value及key两个参数不具有响应性,但是它们应该都具备响应性,需要将这两个参数转成响应体。
// mutableInstrumentation 对象的forEach方法
forEach(callback) {const target = this.rawconst wrap = (val) => typeof val === 'object' ? reactive(val) : valtrack(target,ITERATE_KEY)target.forEach((v,k) => {callback(wrap(v),wrap(k),this)})
}function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {// 省略其他代码if (type === 'ADD' || type === 'DELETE' || (type === 'SET' && Object.prototype.toString.call(target) === '[object Map]')) {// 省略其他代码}// 省略其他代码 }
}
1.4 迭代器方法
集合类型有三个迭代器方法:entries、keys、values,还可以使用 for...of进行迭代。
- 使用for...of迭代一个代理对象时,内部会调用[Symbol.iterator]()方法,返回一个迭代器。迭代产生的值不具备响应性,所以需要把这些值包装成响应体。
- 可迭代协议指一个对象实现了Symbol.iterator方法,迭代器协议是指一个对象实现了next方法。而entries方法要求返回值是一个可迭代对象,即该对象要实现了Symbol.iterator方法。
- values 方法,返回的仅是Map的值,而非键值对。
- keys 方法,与上面不同的是,调用set时,如果非添加值,则不应该触发响应。
function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {// 省略其他代码if (type === 'ADD' || type === 'DELETE' && Object.prototype.toString.call(target) === '[object Map]') {const tempSet = map.get(MAP_KEY_ITERATE_KEY)tempSet && tempSet.forEach(fn => {if (activeEffectFun !== fn) addSet.add(fn)})}addSet.forEach(fn => fn())}
}const mutableInstrumentation = {// 省略其他代码[Symbol.iterator]: iterationMethod,entries: iterationMethod,values: valueIterationMethod,keys: keyIterationMethod
}function iterationMethod() {const target = this.rawconst itr = target[Symbol.iterator]()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: value ? [wrap(value[0]),wrap(value[1])] : value,done}},[Symbol.iterator]() {return this}}
}
function valueIterationMethod() {const target = this.rawconst itr = target.values()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}
function keyIterationMethod() {const target = this.rawconst itr = target.keys()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,MAP_KEY_ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}
2 原始值的响应方案ref
Proxy 的代理目标必须是非原始值,如果要让原始值具有响应性,那么要对它进行包装。Vue3 的ref函数就负责这个工作。
function ref(val) {const wrapper = {value: val}// 为了区分数据是经过ref包装的,还是普通对象Object.defineProperty(wrapper,'_v_isRef',{value: true})return reactive(wrapper)
}
2.1 reactive 解构时丢失响应性
const proxyObj = reactive({name: 'hmf',num: 1})
const obj = {...proxyObj} // obj 不再具有响应性。这是因为解构时
{…proxyObj} 等价于 {name: 'hmf',num: 1}
要让obj 具有响应性,则需要使其属性值为一个对象。如下所示:
const obj = {name: {get value() {return proxyObj.name},set value(val) {proxyObj['name'] = val}},num: {get value() {return proxyObj.num},set value(val) {proxyObj['num'] = val}}
}
ref函数优化如下:
function toRefs(obj) {const ret = {}for (const key in obj) {ret[key] = toRef(obj,key)}return ret
}function toRef(obj,key) {const wrapper = {get value() {return obj[key]},set value(val) {obj[key] = val}}Object.defineProperty(wrapper,'_v_isRef',{value: true})return wrapper
}
2.2 自动脱ref
经过toRefs处理的对象,都需要通过对象的value属性来访问,例如
const proxyObj = ref({name: 'hmf',num: 1})
console.log(proxyObj.name.value)
proxyObj.name.value = 'hi'
访问任何属性都需要通过value属性访问,这增加了用户的心智负担。我们需要自动脱ref的能力,即上面proxy.name就可直接访问。
function proxyRefs(target) {return new Proxy(target, {get(target, p, receiver) {const value = Reflect.get(target,p,receiver)return value._v_isRef ? value.value : value},set(target, p, newValue, receiver) {const value = target[p]if (value._v_isRef) {value.value = newValuereturn true}return Reflect.set(target,p,newValue,receiver)},})
}
相关文章:
vue3 reactive原理(二)-代理Set和Map及ref原理
Set和Map类型的数据也属于异质对象,它们有特定的属性和方法用来操作自身。因此创建代理时,针对特殊的方法需要特殊的对待。 Vue 的ref 是基于reactive函数实现的,它在其基础上,增加了基本类型的响应性、解决reactive在解构时丢失…...
Python自然语言处理库之NLTK与spaCy使用详解
概要 自然语言处理(NLP)是人工智能和数据科学领域的重要分支,致力于让计算机理解、解释和生成人类语言。在Python中,NLTK(Natural Language Toolkit)和spaCy是两个广泛使用的NLP库。本文将详细介绍NLTK和spaCy的特点、功能及其使用方法,并通过具体示例展示如何使用这两…...
Hive-内部表和外部表
区别 内部表实例 准备数据 查看数据 删除数据 外部表实例 准备数据 查看数据 删除数据 区别 内部表:管理元数据(记录数据的文件和目录的信息)和数据。当删除内部表时,会删除数据和表的元数据,所以当多个表关…...
Java并发编程(三)
Java并发编程 1、什么是 Executors 框架 Executors框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。 无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以…...
Flink Doirs Connector 常见问题:Doris目前不支持流读
常见问题 Doris Source 在数据读取完成后,流为什么就结束了? 目前 Doris Source 是有界流,不支持 CDC 方式读取。 问题:对于 Flink Doris DataStream,Flink 想要在 流式读取 Doirs / 实时读 Doris,目前读…...
期末复习资料——计算机系统基础
第一章 1、下列关于机器字长、指令字长和存储字长的说法中,正确的时_②、③_ ①三者在数值上总是相等的。②三者在数值上可能不相等。③存储字长是存放在一个存储单元中的二进制代码位数。④数据字长就是MDR的位数。 机器字长、指令字长和存储字长,三…...
一天搞定Recat(5)——ReactRouter(上)【已完结】
Hello!大家好,今天带来的是React前端JS库的学习,课程来自黑马的往期课程,具体连接地址我也没有找到,大家可以广搜巡查一下,但是总体来说,这套课程教学质量非常高,每个知识点都有一个…...
TCP/IP 网络模型详解(二)之输入网址到网页显示的过程
当键入网址后,到网页显示,其间主要发生了以下几个步骤: 一、解析URL 下图是URL各个元素所表示的意义: 右边蓝色部分(文件的路径名)可以省略。当没有该数据时,代表访问根目录下事先设置的默认文…...
【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法
【k8s故障处理篇】calico-kube-controllers状态为“ImagePullBackOff”解决办法 一、环境介绍1.1 本次环境规划1.2 kubernetes简介1.3 kubernetes特点二、本次实践介绍2.1 本次实践介绍2.2 报错场景三、查看报错日志3.1 查看pod描述信息3.2 查看pod日志四、报错分析五、故障处理…...
SAP PP学习笔记31 - 计划运行的步骤2 - Scheduling(日程计算),BOM Explosion(BOM展开)
上一章讲了计划运行的5大步骤中的前两步,计算净需求和计算批量大小。 SAP PP学习笔记30 - 计划运行的步骤1 - Net requirements calculation 计算净需求(主要讲了安全库存要素),Lot-size calculation 计算批量大小-CSDN博客 本章继续讲计划运行的后面几…...
[vue3]配置@指向src
在vit.config.ts里的export default defineConfig添加以下语句 resolve: {alias: {"": "/src", // 配置指向src目录},},...
【多模态大模型】 BLIP in ICML 2022
一、引言 论文: BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 作者: Salesforce Research 代码: BLIP 特点: 该方法分别使用ViT和BERT进行图像和文本特征提取&am…...
Flutter开发Dart 中的 mixin、extends 和 implements
目录 前言 1.extends 2.implements 3.mixin 前言 在 Dart 中,mixin、extends 和 implements 是面向对象编程中常用的关键字,它们分别用于不同的继承和实现方式。理解它们的用法和区别对于编写高质量、可维护的 Dart 代码至关重要。本文…...
SAPUI5基础知识20 - 对话框和碎片(Dialogs and Fragments)
1. 背景 在 SAPUI5 中,Fragments 是一种轻量级的 UI 组件,类似于视图(Views),但它们没有自己的控制器(Controller)。Fragments 通常用于定义可以在多个视图中重用的 UI 片段,从而提…...
express连接mysql
一、 安装express npm install express --save二、express配置 //引入 const express require("express"); //创建实例 const app express(); //启动服务 app.listen(8081, () > {console.log("http://localhost:8081"); });三、安装mysql npm i m…...
24暑假算法刷题 | Day24 | LeetCode 93. 复原 IP 地址,78. 子集,90. 子集 II
目录 93. 复原 IP 地址题目描述题解 78. 子集题目描述题解 90. 子集 II题目描述题解 93. 复原 IP 地址 点此跳转题目链接 题目描述 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用…...
Postman本地化测试全攻略:打造多语言API的秘诀
Postman本地化测试全攻略:打造多语言API的秘诀 在全球化的今天,许多应用程序都需要支持多语言环境,以满足不同地区用户的需求。API的本地化测试是确保应用程序能够在不同语言和区域设置下正确运行的关键环节。Postman作为一个强大的API开发和…...
摆弄it:越走越深
在英语中,it是一个单词,就是“它”,这是众所周知的事情。今天,我们就来摆弄一下it,摆弄一下“它”,看看能摆弄出什么名堂来。 一、它是它自己 it 大家都知道,同样,itself࿰…...
网页上空格
  no-break space(普通的英文半角空格但不换行) 中文全角空格 (一个中文宽度)   en空格(半个中文宽度)   em空格 (一个中文宽度) 四分之一em空格 (四分之一中文宽度) 相比平时的空格(), 拥有不间断(non-breaking)特性。即连续…...
Linux服务管理(四)Apache服务
Apache服务 1、基于IP的虚拟主机2、基于IP端口的虚拟主机3、基于域名的虚拟主机4、prefork模式5、worker模式6、event模式7、细说驱动工作模式和MPM(多处理模块)工作模式 新旧域名都保留,因为旧域名已有一定的知名度和流量,直接下…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
