当前位置: 首页 > news >正文

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 合理触发

为了提供性能及用户交互,应当满足下列条件时,才触发响应:

  1. 值发生改变时。
  2. 初始值和新值不都是NaN。
  3. 响应对象的原型也说响应对象时,访问原型上的属性时,只触发一次。
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

  1. 通过索引设置元素值时,可能会影响到length属性,即当设置索引值大等于数组长度时,length属性会发生改变。
  2. 设置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函数会为属性中的对象也创建响应对象,而且每次都会创建新的响应对象。而且,这里还有两个问题:

  1. 将响应体对象赋值给另一个响应体时,reactive不应该为其再创建响应体了。
  2. 无论查找对象,还是其响应体对象,返回的结果应该一致(从业务的角度看,它们属于同一对象)。
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 只能拦截对一个对象的基本操作&#xff08;例如读取、设置属性值&#xff09;&#xff0c;而无法拦截复合操作&#xff08;例如&#xff0c;obj.fun(),由两个基本操作组成&#xff0c;1&#xff09;get到fun这个属性&#xff0c;2&#xff09;函数调用&#xff09;。 1 …...

基于联咏 NT98692芯片赋能边缘计算IP摄像机与XVR监控系统解决方案

联咏 NT98692 是一款新世代整合度极高的 SoC&#xff0c;具有高影像品质、低位元率、低功耗&#xff0c;针对 8Kp30 边缘运算 IP 摄影机与后端监控系统 XVR 应用。此 SoC 整合了 ARM Quad Cortex A73 CPU 核心、新一代 ISP 和 AI ISP、H.265/H.264 视讯压缩编解码器、DSP、高效…...

Python设计模式 - 工厂方法模式

定义 工厂方法模式是一种创建型设计模式&#xff0c;它定义一个创建对象的接口&#xff0c;让其子类来处理对象的创建&#xff0c;而不是直接实例化对象。 结构 抽象工厂&#xff08;Factory&#xff09;&#xff1a;声明工厂方法&#xff0c;返回一个产品对象。具体工厂类都…...

学习记录:ESP32控制舵机 FREERTOS BLE

控制舵机 PWM信号 PWM信号是一种周期性变化的方波信号&#xff0c;它有两个关键参数&#xff1a; 周期&#xff08;Period&#xff09;&#xff1a;一个完整的PWM信号的时间长度&#xff0c;通常用秒&#xff08;s&#xff09;或毫秒&#xff08;ms&#xff09;表示。占空比…...

react中的useState和Hook、副作用

react的组件分为类组件和函数组件&#xff0c;Hook 是一种特殊的函数&#xff0c;可以让你在函数组件中使用类组件中才有的一些特性。useState、useEffect、useReducer都是Hook。其中useState用于在函数组件中添加状态&#xff0c;useEffect用于在函数组件中执行副作用&#xf…...

Linux嵌入式学习——数据结构——线性表的链式结构

线性表的链式存储 解决顺序存储的缺点&#xff0c;插入和删除&#xff0c;动态存储问题。 特点&#xff1a; 线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素&#xff0c;存储单元可以是连续的&#xff0c;也可以不连续。可以被存储在任意内存未被占…...

文本编辑 文本中的各种空格

参考资料 欧文の半角スペースは&#xff12;種類ある!?无中断空格常见空格一览浅析什么是零宽度字符以及零宽度字符在实际中的应用场景空格象形字间隔无中断空格零宽间隔 目录 零. 各种空格在Notepad中的效果一. 半角空格二. 全角空格三. TAB空格四. 无中断空格4.1 定义4.2 H…...

Vue插槽 (Slots)详解

目录 前言基础插槽具名插槽作用域插槽默认插槽动态插槽名总结相关阅读 前言 Vue的插槽&#xff08;Slots&#xff09;是一个非常强大的特性&#xff0c;它允许你在组件的模板中嵌入父组件的内容。插槽使得组件之间的内容分发变得灵活&#xff0c;尤其在构建可复用组件时非常…...

Unity中有关Animation的一点笔记

也许更好的阅读体验 Animation Unity中Animation类并不是直接记载了和播放动画有关的信息&#xff0c;可以简单理解Animation为一个动画播放器&#xff0c;播放的具体内容就像卡带一样&#xff0c;当我们有了卡带后我们可以播放动画。 对应的则是编辑器中的组件 所以Anima…...

module federation模块联邦与微前端

module federation是什么 webpack5新增了module federation&#xff0c;module federation的作用&#xff0c;将每个构建(build)作为容器(这是一个概念)&#xff0c;构建后的资源可以正常部署&#xff0c;同时还具备在运行时对外暴露其中的模块&#xff0c;这就意味着多个构建…...

日常开发记录分享——C#控件ToolTip实现分栏显示内容

文章目录 需求来源实现思路实施请看VCR等等别走&#xff0c;有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息&#xff0c;一开始使用的tooltip实现&#xff0c;但是里面的内容效果并不理想&#xff0c;需要有条理性&#xff0c;于是就想到能不能将展示的东西分列…...

Kettle下载安装

环境说明 虚拟机&#xff1a;Win7&#xff1b;MySql8.0 主机&#xff1a;Win11&#xff1b;JDK1.8&#xff1b;Kettle 9.4&#xff08;Pentaho Data Integration 9.4&#xff09;&#xff08;下载方式见文末&#xff09; 安装说明 【1】解压后运行Spoon.bat 【2】将jar包 复…...

最新版Golang pprof使用(引入、抓取、分析,图文结合)

最新版Golang pprof使用 &#x1f525;具体实践: Go调试神器pprof使用教程Golang线上内存爆掉问题排查&#xff08;pprof&#xff09; 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组件通信的工具&#xff0c;因为vue3的script可以设置为setup&#xff0c;写法同vue2有较大区别。 一、script setup - 直接写 <script lang"ts" setup>const emit defineEmits([close]);function handleClose() {emit(close);}…...

Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode

第一部分&#xff1a;Node.js 第一步&#xff1a;下载Node.js 方法1&#xff1a;链接 下载 | Node.js 中文网 (nodejs.cn) 方法2&#xff1a;百度网盘 链接&#xff1a;https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码&#xff1a;aurk --来自百度网盘…...

【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式

这是【Dart 教程系列第 49 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.22.1、Dart SDK&#xff1a;3.4.1 文章目录 一&#xff1a;什么是策略设计模式&#xff1f;二&#xff1a;为什么要使用策略设计模式&#xff1…...

BGP路由反射器

原理概述 缺省情况下&#xff0c;路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体&#xff0c;这个原则称为BGP水平分割原则&#xff0c;该原则的根本作用是防止 AS内部的BGP路由环路。因此&#xff0c;在AS内部&#xff0c;一般需要每台…...

DolphinDB Web 端权限管理:可视化操作指南

在现代数据库管理中&#xff0c;高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限&#xff0c;这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率&#xff0c;DolphinDB 目前在 Web 上…...

学习Vue2收藏这一篇就够了(如何创建Vue实例)

什么是Vue&#xff1f; Vue是什么&#xff1a;是一个用于构建用户界面的渐进式框架 什么是构建用户界面&#xff1a;基于数据动态渲染页面 什么是渐进式&#xff1a;循序渐进的学习 什么是框架&#xff1a;一整套完整的项目解决方案 创建Vue实例 核心步骤&#xff08;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…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...