Vue3 reactive原理(一)-代理对象及数组
Proxy 只能拦截对一个对象的基本操作(例如读取、设置属性值),而无法拦截复合操作(例如,obj.fun(),由两个基本操作组成,1)get到fun这个属性,2)函数调用)。
1 代理对象
| 操作 | 跟踪方法 | 触发方法 |
| 访问属性,obj.name | get(traget,p) | set、delete |
| 判断对象或原型上是否存在给定的key, key in obj | has(target,p) | delete,set |
| 使用for...in遍历对象 | ownkeys(target) | delete,set(添加新属性时) |
表 普通对象所有可能的读取操作
ownkeys方法没有具体的属性,这时我们指定一个唯一符号:ITERATE_KEY。来将它与副作用函数关联。
const ITERATE_KEY = Symbol("ITERATE_KEY")
return new Proxy(obj,{// 省略其他代码set(target, p, newValue, receiver) {const type = Object.prototype.hasOwnProperty.call(target,p) ? 'SET' : 'ADD'const res = Reflect.set(target,p,newValue,receiver)trigger(target,p,type,newValue)return res},deleteProperty(target, p) {const res = Reflect.deleteProperty(target,p)trigger(target,p,'DELETE')return res },has(target, p) {track(target,p)return Reflect.has(target,p)},ownKeys(target) {track(target,ITERATE_KEY)return Reflect.ownKeys(target)}
})
function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {const addSet = new Set()// 省略其他代码if (type === 'ADD') {const tempSet = map.get(ITERATE_KEY)tempSet && tempSet.forEach(fn => {if (activeEffect !== fn) addSet.add(fn)})}addSet.forEach(fn => fn())}
}
1.1 合理触发
为了提供性能及用户交互,应当满足下列条件时,才触发响应:
- 值发生改变时。
- 初始值和新值不都是NaN。
- 响应对象的原型也说响应对象时,访问原型上的属性时,只触发一次。
return new Proxy(obj,{get(target, p, receiver) {if (p === 'raw') return target// 省略其他代码},set(target, p, newValue, receiver) {// 省略其他代码if (receiver.raw === target) {if (oldVal !== newValue && (newValue === newValue || oldVal === oldVal)) {trigger(target,p,type)}}},deleteProperty(target, p) {const hadKey = Object.prototype.hasOwnProperty.call(target,p)const res = Reflect.deleteProperty(target,p)if (hadKey && res) {trigger(target,p,'DELETE')}},
})
1.2 深响应与浅响应
目前的reactive函数创建的响应对象是浅响应,即对象的首层才具有响应性。如果对象的某个属性值是个对象,那么该对象不具备响应性。而深响应是指无论多少层,对象都具有响应性。
function createReactive(obj,isShallow = false) {if (obj.raw) return objreturn new Proxy(obj,{get(target, p, receiver) {// 省略其他代码const val = Reflect.get(target,p,receiver)if (isShallow) return valreturn typeof val === 'object' && val != null ? createReactive(val) : val},// 省略其他代码})
}
1.2.1 深只读和浅只读
某些数据要是只读的(例如props),当用户尝试修改只读数据时,会收到一条警告信息。浅只读是指,对象的首层不可写,但是其他层可写(对象的一个属性值也是对象时,那么这个属性值对象里的属性是可写的)。
function createReactive(obj,isShallow = false,isReadonly) {if (obj.raw) return objreturn new Proxy(obj,{get(target, p, receiver) {// 省略其他代码const val = Reflect.get(target,p,receiver)if (isShallow) return valreturn typeof val === 'object' && val != null ? (isReadonly ? readonly(val) : createReactive(val)) : val},set(target, p, newValue, receiver) {if (isReadonly) {console.error('该属性不可写')return}// 省略其他代码},deleteProperty(target, p) {if (isReadonly) {console.error('该属性不可写')return}// 省略其他代码},// 省略其他代码})
}function readonly(obj,isShallow = false) {return createReactive(obj,isShallow,true)
}
2 代理数组
数组也是一种对象(但属于异质对象,与常规对象相比,它们的部分内部方法和常规对象的不同)。因此用于代理普通对象的大部份代码可以继续使用。
2.1 索引与length
- 通过索引设置元素值时,可能会影响到length属性,即当设置索引值大等于数组长度时,length属性会发生改变。
- 设置length属性,可能会影响到索引值。当length设置为更小值时,索引大等于length的部分元素全部会被删除。
new Proxy(obj,{set(target, p, newValue, receiver) {// 省略其他代码const type = Array.isArray(target) ? (Number(p) < target.length ? 'SET' : 'ADD') :(Object.prototype.hasOwnProperty.call(target,p) ? 'SET' : 'ADD')// 省略其他代码},// 省略其他代码
})function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {// 省略其他代码if (type === 'ADD' && Array.isArray(target)) {const tempSet = map.get("length")tempSet && tempSet.forEach(fn => {if (activeEffect !== fn) addSet.add(fn)})}if (p === 'length' && Array.isArray(target)) {map.forEach((set,key) => {if (key >= newValue) {set.forEach(fn => {if (activeEffect !== fn) addSet.add(fn)})}})}addSet.forEach(fn => fn())}
}
2.2 遍历数组
1)for...in,和普通对象一样,内部会调用ownKeys方法,但不同的是,其触发条件是length的改变。
new Proxy(obj,{ownKeys(target) {track(target,Array.isArray(target) ? 'length' : ITERATE_KEY)return Reflect.ownKeys(target)}// 省略其他代码
})
2)for...of,索引值的设置及length的改变,都会触发该迭代,所以几乎不要添加额外的代码,就能让for...for迭代具有响应性。
从性能上及为了避免发生意外的错误,我们不应该使副作用函数与symbol值之间建立响应联系。
2.3 数组的查找方法
数组的方法内部其实都依赖了对象的基本语义。因此大多数情况下,我们不需要做特殊处理即可让这些方法按预期工作。但某些场景,执行结果会不符合我们预期:
const proxyObj = reactive(['hi','js',{name: 'js'}])console.log(proxyObj.includes('hi'),proxyObj.includes(proxyObj[2])) // true、false
这里,查找基本类型数据时,结果是正确的。但是查找对象时,结果错误。这是因为reactive函数会为属性中的对象也创建响应对象,而且每次都会创建新的响应对象。而且,这里还有两个问题:
- 将响应体对象赋值给另一个响应体时,reactive不应该为其再创建响应体了。
- 无论查找对象,还是其响应体对象,返回的结果应该一致(从业务的角度看,它们属于同一对象)。
const reactiveMap = new WeakMap()new Proxy(obj,{get(target, p, receiver) {if (p === 'raw') return target// 省略其他代码if (Array.isArray(target) && arrayInstrumentation.hasOwnProperty(p)) {return Reflect.get(arrayInstrumentation,p,receiver)}// 省略其他代码},
})function reactive(obj) {const originalProxy = reactiveMap.get(obj)if (originalProxy) return originalProxy;const proxyObj = createReactive(obj)reactiveMap.set(obj,proxyObj)return proxyObj
}const arrayInstrumentation = {};
['includes','indexOf','lastIndexOf'].forEach(method => {const originalMethod = Array.prototype[method]arrayInstrumentation[method] = function (...args) {let res = originalMethod.apply(this,args)if (res === false || res === -1) {res = originalMethod.apply(this.raw,args)}return res}
});
2.4 隐式修改数组长度的原型方法
push/pop/shift/unshift/splice这些方法会隐式地修改数组长度。例如push方法,在往数组中添加元素时,既会读取length,也会设置数组的length。这会导致两个独立的副作用函数互相影响:
effect(() => {proxyObj.push(1)
})
effect(() => {proxyObj.push(2)
})
运行上面这段代码,会得到栈溢出的错误。因为副作用函数1执行时,会修改及读取length,会触发副作用函数2执行,而副作用2也会修改和读取length。这样就好造成循环调用。
解决方法是:“屏蔽”这些方法对length属性的读取。
['push','pop','shift','unshift','splice'].forEach(method => {const originalMethod = Array.prototype[method]arrayInstrumentation[method] = function (...args) {shouldTrack = falseconst res = originalMethod.apply(this,args)shouldTrack = truereturn res}
})function track(target,p) {if (activeEffect && shouldTrack) {// 跟踪代码}
}相关文章:
Vue3 reactive原理(一)-代理对象及数组
Proxy 只能拦截对一个对象的基本操作(例如读取、设置属性值),而无法拦截复合操作(例如,obj.fun(),由两个基本操作组成,1)get到fun这个属性,2)函数调用)。 1 …...
基于联咏 NT98692芯片赋能边缘计算IP摄像机与XVR监控系统解决方案
联咏 NT98692 是一款新世代整合度极高的 SoC,具有高影像品质、低位元率、低功耗,针对 8Kp30 边缘运算 IP 摄影机与后端监控系统 XVR 应用。此 SoC 整合了 ARM Quad Cortex A73 CPU 核心、新一代 ISP 和 AI ISP、H.265/H.264 视讯压缩编解码器、DSP、高效…...
Python设计模式 - 工厂方法模式
定义 工厂方法模式是一种创建型设计模式,它定义一个创建对象的接口,让其子类来处理对象的创建,而不是直接实例化对象。 结构 抽象工厂(Factory):声明工厂方法,返回一个产品对象。具体工厂类都…...
学习记录:ESP32控制舵机 FREERTOS BLE
控制舵机 PWM信号 PWM信号是一种周期性变化的方波信号,它有两个关键参数: 周期(Period):一个完整的PWM信号的时间长度,通常用秒(s)或毫秒(ms)表示。占空比…...
react中的useState和Hook、副作用
react的组件分为类组件和函数组件,Hook 是一种特殊的函数,可以让你在函数组件中使用类组件中才有的一些特性。useState、useEffect、useReducer都是Hook。其中useState用于在函数组件中添加状态,useEffect用于在函数组件中执行副作用…...
Linux嵌入式学习——数据结构——线性表的链式结构
线性表的链式存储 解决顺序存储的缺点,插入和删除,动态存储问题。 特点: 线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素,存储单元可以是连续的,也可以不连续。可以被存储在任意内存未被占…...
文本编辑 文本中的各种空格
参考资料 欧文の半角スペースは2種類ある!?无中断空格常见空格一览浅析什么是零宽度字符以及零宽度字符在实际中的应用场景空格象形字间隔无中断空格零宽间隔 目录 零. 各种空格在Notepad中的效果一. 半角空格二. 全角空格三. TAB空格四. 无中断空格4.1 定义4.2 H…...
Vue插槽 (Slots)详解
目录 前言基础插槽具名插槽作用域插槽默认插槽动态插槽名总结相关阅读 前言 Vue的插槽(Slots)是一个非常强大的特性,它允许你在组件的模板中嵌入父组件的内容。插槽使得组件之间的内容分发变得灵活,尤其在构建可复用组件时非常…...
Unity中有关Animation的一点笔记
也许更好的阅读体验 Animation Unity中Animation类并不是直接记载了和播放动画有关的信息,可以简单理解Animation为一个动画播放器,播放的具体内容就像卡带一样,当我们有了卡带后我们可以播放动画。 对应的则是编辑器中的组件 所以Anima…...
module federation模块联邦与微前端
module federation是什么 webpack5新增了module federation,module federation的作用,将每个构建(build)作为容器(这是一个概念),构建后的资源可以正常部署,同时还具备在运行时对外暴露其中的模块,这就意味着多个构建…...
日常开发记录分享——C#控件ToolTip实现分栏显示内容
文章目录 需求来源实现思路实施请看VCR等等别走,有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息,一开始使用的tooltip实现,但是里面的内容效果并不理想,需要有条理性,于是就想到能不能将展示的东西分列…...
Kettle下载安装
环境说明 虚拟机:Win7;MySql8.0 主机:Win11;JDK1.8;Kettle 9.4(Pentaho Data Integration 9.4)(下载方式见文末) 安装说明 【1】解压后运行Spoon.bat 【2】将jar包 复…...
最新版Golang pprof使用(引入、抓取、分析,图文结合)
最新版Golang pprof使用 🔥具体实践: Go调试神器pprof使用教程Golang线上内存爆掉问题排查(pprof) Github地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-pprof 引入pprof:import _ “net/http/pprof” …...
vue3学习记录1:emit的写法
emit是用于child组件向parent组件通信的工具,因为vue3的script可以设置为setup,写法同vue2有较大区别。 一、script setup - 直接写 <script lang"ts" setup>const emit defineEmits([close]);function handleClose() {emit(close);}…...
Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode
第一部分:Node.js 第一步:下载Node.js 方法1:链接 下载 | Node.js 中文网 (nodejs.cn) 方法2:百度网盘 链接:https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码:aurk --来自百度网盘…...
【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式
这是【Dart 教程系列第 49 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Flutter SDK:3.22.1、Dart SDK:3.4.1 文章目录 一:什么是策略设计模式?二:为什么要使用策略设计模式࿱…...
BGP路由反射器
原理概述 缺省情况下,路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体,这个原则称为BGP水平分割原则,该原则的根本作用是防止 AS内部的BGP路由环路。因此,在AS内部,一般需要每台…...
DolphinDB Web 端权限管理:可视化操作指南
在现代数据库管理中,高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限,这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率,DolphinDB 目前在 Web 上…...
学习Vue2收藏这一篇就够了(如何创建Vue实例)
什么是Vue? Vue是什么:是一个用于构建用户界面的渐进式框架 什么是构建用户界面:基于数据动态渲染页面 什么是渐进式:循序渐进的学习 什么是框架:一整套完整的项目解决方案 创建Vue实例 核心步骤(4步…...
Mysql数据库第四次作业
mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…...
PP实战指南:ECN工程变更在物料计划中的关键应用与系统操作解析
1. ECN工程变更的核心价值与业务场景 第一次接触ECN(Engineering Change Notice)是在2015年负责汽车零部件项目时,当时产线因为一个螺丝规格变更导致全线停产8小时。这个惨痛教训让我深刻理解到,工程变更绝不是简单的技术文档更新…...
移动端语音交互避坑指南:录音超时截取、倒计时提醒与MP3转换的完整方案
移动端语音交互避坑指南:录音超时截取、倒计时提醒与MP3转换的完整方案 在即时通讯和语音输入场景中,流畅的录音体验直接影响用户留存。数据显示,超过83%的用户会因为录音功能卡顿或操作复杂而放弃使用语音功能。本文将深入解析三个关键体验优…...
Go 协程池设计与调度实现
Go 协程池设计与调度实现 在并发编程中,Go 语言的协程(goroutine)以其轻量级和高性能著称。无限制地创建协程可能导致资源耗尽,影响系统稳定性。为此,协程池的设计与调度成为优化高并发场景的重要手段。本文将深入探讨…...
麒麟V10系统下国产海量数据库安装全攻略(含内核参数优化与避坑指南)
麒麟V10系统下国产海量数据库安装全攻略(含内核参数优化与避坑指南) 在国产化技术快速发展的今天,越来越多的企业和机构开始采用国产操作系统和数据库产品。麒麟V10作为国产操作系统的代表之一,其稳定性和安全性得到了广泛认可。而…...
SystemVerilog内存操作实战:手把手教你实现AXI VIP中的backdoor读写
SystemVerilog内存操作实战:AXI VIP中的backdoor读写技术解析 在硬件验证领域,AXI总线协议因其高性能和灵活性已成为行业标准。验证工程师经常需要与AXI VIP(Verification IP)交互,其中内存操作是最基础也最关键的环节…...
2026 AI大模型岗位薪资全曝光:从30k到80w,程序员必备指南,非常详细收藏我这一篇就够了
文章主要展示了2026年AI领域热门岗位的薪资情况,包括华为、腾讯、联影等公司在多个城市的AI工程师、大模型算法等职位的薪资水平。数据显示AI人才市场需求旺盛,薪资从月薪3.6万到年包80万不等。文章提供了AI薪资专场的链接,邀请读者了解更多行…...
ArduPilot电机控制逻辑与PWM输出机制剖析
1. ArduPilot电机控制基础概念 当你第一次接触无人机飞控时,最让人困惑的莫过于电机控制逻辑了。想象一下,你手里拿着遥控器,轻轻推动摇杆,无人机就能平稳地上升、下降或者转向。这背后到底发生了什么?让我用最直白的…...
Zotero插件Ethereal Style:打造高效文献管理新体验
Zotero插件Ethereal Style:打造高效文献管理新体验 【免费下载链接】zotero-style zotero-style - 一个 Zotero 插件,提供了一系列功能来增强 Zotero 的用户体验,如阅读进度可视化和标签管理,适合研究人员和学者。 项目地址: ht…...
python基于微信小程序的方言文化传播平台的设计与开发
目录需求分析与规划技术选型与架构设计核心功能实现数据处理与优化测试与部署运营与迭代项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作需求分析与规划 明确平台的核心功能需求,包括方言内容展示、语音录制与分享、…...
LangGPT结构化提示词框架:重新定义AI交互的核心方法
LangGPT结构化提示词框架:重新定义AI交互的核心方法 【免费下载链接】LangGPT LangGPT: Empowering everyone to become a prompt expert!🚀 Structured Prompt,Language of GPT, 结构化提示词,结构化Prompt 项目地址: https://…...
