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(多处理模块)工作模式 新旧域名都保留,因为旧域名已有一定的知名度和流量,直接下…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...