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

Vue3.js“非原始值”响应式实现基本原理笔记(二)

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:前端小王hs

阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来

书籍:《Vue.js设计与实现》 作者:霍春阳

本篇博文将在书第5.1节5.4节的基础上进一步总结所提到的基础概念,附加了测试的代码运行示例,方便正在学习Vue3想分析Vue3源码的朋友快速阅读

如有帮助,不胜荣幸

前文:
Vue3.js“非原始值”响应式实现基本原理笔记(一)

如何代理Object

“读取”是一个很宽泛的概念(原文)

在之前的笔记中,只简单的讨论了如obj.foo这般获取对象属性值的读取,但读取有很多种,例如下面几种:

  1. 访问属性:obj.foo
  2. 判断:key in obj
  3. 遍历:for (const key in obj)

这一章的内容,就是针对这几种不同的读取,以及其他的常见行为如删除等进行拦截

实现的逻辑主要是看操作符对应的拦截函数

在书中是通过查阅ECMA规范,明确操作符运行逻辑,进而找到操作符的运算结果是调用什么抽象方法,然后通过这个抽象方法找到对应的内部方法,进而对比Vue3.js“非原始值”响应式实现基本原理笔记(一)提到的Proxy内部方法表,选取对应的方法拦截

拦截 in

下面in操作符为例,我们来看一下书中是如何逐步找到拦截方法

ECMA-262规范的13.10.1(原文),找到in操作符的运行时逻辑:

  1. 让 lref 的值为 RelationalExpression 的执行结果。
  2. 让 lval 的值为 ? GetValue(lref)。
  3. 让 rref 的值为 ShiftExpression 的执行结果。
  4. 让 rval 的值为 ? GetValue(rref)。
  5. 如果 Type(rval) 不是对象,则抛出 TypeError 异常。
  6. 返回 ? HasProperty(rval, ?
    ToPropertyKey(lval))。

关键是第6步,出现了HasProperty(),然后在在ECMA-262规范的7.3.11(原文)找到关于这个方法的逻辑:

  1. 断言:Type(O) 是 Object。
  2. 断言:IsPropertyKey§ 是 true。
  3. 返回 ? O.[[HasProperty]] §。

可以发现这个内部方法[[HasProperty]],然后在表中找到对应的拦截函数——has,如下图所示:

image.png

然后就可以在Proxyhandler中使用has进行拦截了,代码如下:

const obj = { foo: 1 }
const p = new Proxy(obj, {has(target, key) {track(target, key)return Reflect.has(target, key)}
})effect(() => {'foo' in p // 将会建立依赖关系
})

第5章有非常多的关于ECMA的运行时逻辑,在书中没有解释,所以笔者在这里还是简单介绍一下一些关键词(以上述in的运行时逻辑为例):

  1. RelationalExpression:关系表达式,例如<>=ininstanceof<=>=等操作符,在这里指的操作符in左侧的表达式
  2. **ShiftExpression**:位移操作的表达式,操作符in右侧的表达式
  3. lreflvalRelationalExpression会生成一个引用lref,然后通过GetValue(lref)得到lval
  4. rreflref:逻辑同上

拦截 for…in(遍历所有可枚举属性)

逻辑和in是相同的,找规范找到最后发现可以使用ownKey()去拦截

但是在ownKey中会做一些处理,代码如下:

const obj = { foo: 1 }
const ITERATE_KEY = Symbol()const p = new Proxy(obj, {ownKeys(target) {// 将副作用函数与 ITERATE_KEY 关联track(target, ITERATE_KEY)return Reflect.ownKeys(target)}
})

注:这本书的特点就是类似于电视连续剧,整一章节的内容是不断累积的,所以的时候不要间断,同时要多复习

在之前的笔记中,track是传入targetkey,代码如下:

function track(target, key) {// 没有 activeEffect,直接 returnif (!activeEffect) return;let depsMap = bucket.get(target);if (!depsMap) {bucket.set(target, (depsMap = new Map()));}let deps = depsMap.get(key);if (!deps) {depsMap.set(key, (deps = new Set()));}const array = Array.from(deps);deps.add(activeEffect);activeEffect.deps.push(deps);
}

现在变成了传入ITERATE_KEYiterate的意思是重复

为什么传入ITERATE_KEY?

因为ownKeys是用来获取一个对象的所有键,不是与任何具体的进行绑定,所以就定义一个ITERATE_KEY作为标识,去与副作用函数进行绑定

那么当触发的时候,也同样需要传入ITERATE_KEY,代码如下:

trigger(target, ITERATE_KEY)

这里需要注意的是,遍历是遍历,触发拦截是触发拦截,整个过程只会触发一次ownKeys,可以理解为执行到for..in时就触发拦截,然后for...in循环遍历自身可枚举的属性

添加属性对for…in的影响

在上述代码中,当执行p.bar=2时,for...in就会由循环一次变为两次(因为obj变为了两个),但此时不会触发与ITERATE_KEY关联的副作用函数

原因非常简单,执行p.bar=2,关联的是与bar相关联的副作用函数

解决的方法也非常简单,在trigger中把两者都添加到effectsToRun

function trigger(target, key) {const depsMap = bucket.get(target)if (!depsMap) return// 取得与 key 相关联的副作用函数const effects = depsMap.get(key)// 取得与 ITERATE_KEY 相关联的副作用函数const iterateEffects = depsMap.get(ITERATE_KEY)const effectsToRun = new Set()// 将与 key 相关联的副作用函数添加到 effectsToRuneffects && effects.forEach(effectFn => {if (effectFn !== activeEffect) {effectsToRun.add(effectFn)}})// 将与 ITERATE_KEY 相关联的副作用函数也添加到effectsToRuniterateEffects && iterateEffects.forEach(effectFn => {if (effectFn !== activeEffect) {effectsToRun.add(effectFn)}})effectsToRun.forEach(effectFn => {if (effectFn.options.scheduler) {effectFn.options.scheduler(effectFn)} else {effectFn()}})
}

这里的ITERATE_KEY是外部定义的Symbol

target即遍历的obj

修改属性对foo…in的影响

修改属性不会对foo...in产生影响,但需要注意的是修改属性新增属性使用的都是[[Set]],所以需要做个区分,代码如下:

const p = new Proxy(obj, {  // 拦截设置操作set(target, key, newVal, receiver) {// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD';// 设置属性值const res = Reflect.set(target, key, newVal, receiver);// 将 type 作为第三个参数传递给 trigger 函数trigger(target, key, type);return res;},// 省略其他拦截函数
});

然后再在trigger中进行判断,如果是ADD时才执行depsMap.get(ITERATE_KEY),代码如下:

function trigger(target, key, type) {  // 省略其他逻辑// 只有当操作类型为 'ADD' 时,才触发与 ITERATE_KEY 相关联的副作用函数  if (type === 'ADD') {  const iterateEffects = depsMap.get(ITERATE_KEY);  iterateEffects && iterateEffects.forEach(effectFn => {if (effectFn !== activeEffect) {effectsToRun.add(effectFn);}});}
}

在书中还提到了定义枚举类型的重要性:

const TriggerType = {SET: 'SET',ADD: 'ADD'
};

便于后期维护

删除属性对for…in的影响

在书中通过查阅规范,得知是通过deleteProperty去拦截的,所以代码如下:

const p = new Proxy(obj, {  deleteProperty(target, key) {  // 检查被操作的属性是否是对象自己的属性  const hadKey = Object.prototype.hasOwnProperty.call(target, key);  // 使用 Reflect.deleteProperty 完成属性的删除  const res = Reflect.deleteProperty(target, key);  if (res && hadKey) {  // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新  trigger(target, key, 'DELETE');  }  return res;  }  
});

这里手写是进行了一个判断,删除的属性是否属于自身,这是因为执行的逻辑可能是如delete p.a,执行了但是这个属性不存在,所以需要进行一个判断

那么最后就是在trigger中继续加多一个type判断,代码如下:

function trigger(target, key, type) {  // 省略其他逻辑// 只有当操作类型为 'ADD' 或 'DELETE' 时才触发 if (type === 'ADD' || type === 'DELETE') {  const iterateEffects = depsMap.get(ITERATE_KEY);  iterateEffects && iterateEffects.forEach(effectFn => {if (effectFn !== activeEffect) {effectsToRun.add(effectFn);}});}
}

其实看到这里,能够发现在设计时是从CURD去考虑的不同情况

总结

这篇笔记主要复习了不同读取情况下的响应式实现:

  1. 如何拦截in操作符
  2. 如何拦截for…in操作符
  3. 当新增、修改和删除时如何实现响应式

相关文章:

Vue3.js“非原始值”响应式实现基本原理笔记(二)

如果您觉得这篇文章有帮助的话&#xff01;给个点赞和评论支持下吧&#xff0c;感谢~ 作者&#xff1a;前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…...

论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型

论文全称&#xff1a;PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题&#xff1a;如何在检索增强式问答&#xff08;ReQA&#xff09;任务中&#xff0c;利用大型语言模型&#xf…...

网络状态的智能感知:WebKit 支持 Network Information API 深度解析

网络状态的智能感知&#xff1a;WebKit 支持 Network Information API 深度解析 在现代 Web 应用中&#xff0c;理解用户的网络连接状态对于提供适应性体验至关重要。Network Information API&#xff0c;一个新兴的 Web API&#xff0c;允许 Web 应用访问设备的网络信息&…...

Vue3基础知识:组合式API中的provide和inject,他们作用是什么?如何使用?以及案例演示

1.provide和inject相较于父子传递的不同在于provide,inject可以用于跨层级通信&#xff08;通俗易懂的讲就是可以实现爷孙之间的直接信息传递&#xff09;。 1.跨层级传递数据 1.在顶层组件通过provide函数提供数据 2.底层组件通过inject函数获取数据 演示一&#xff1a;跨…...

Transformer自注意力机制(Self-Attention)模型

​ 上一篇我们介绍了transform专题一&#xff1a;Seq2seq model&#xff0c;也知道了transfrom属于seq2seq模型&#xff0c;这一排篇咱们接着介绍另外几种seq2seq架构的模型。&#xff09;RNN&#xff08;循环神经网络&#xff09;CNN&#xff08;卷积神经网络&#xff09;&…...

【计算机体系结构】缓存的false sharing

在介绍缓存的false sharing之前&#xff0c;本文先介绍一下多核系统中缓存一致性是如何维护的。 目前主流的多核系统中的缓存一致性协议是MESI协议及其衍生协议。 MESI协议 MESI协议的4种状态 MESI协议有4种状态。MESI是4种状态的首字母缩写&#xff0c;缓存行的4种状态分别…...

Ubuntu24.04 Isaacgym的安装

官方论坛 rl-接口 教程1 教程2 教程3 1.下载压缩包 link 2. 解压 tar -xvf IsaacGym_Preview_4_Package.tar.gz核心教程在 isaacgym/docs/install.html下 3. 从源码安装 Ubuntu24.04还需首先进入虚拟环境 python -m venv myenv # 创建虚拟环境&#xff0c;已有可跳过…...

docker 设置代理,通过代理服务器拉取镜像

docker 拉取目标镜像需要通过代理服务器进行时&#xff0c;可以通过为 docker 配置全局代理来实现。 注&#xff1a;Linux 上通过临时命令 export HTTP_PROXY 设置的代理&#xff0c;对 curl 这些有用&#xff0c;但是对 docker pull 不起作用。 示例 假设您的代理服务器地址是…...

OpenCV教程02:图像处理系统1.0(翻转+形态学+滤波+缩放+旋转)

-------------OpenCV教程集合------------- Python教程99&#xff1a;一起来初识OpenCV&#xff08;一个跨平台的计算机视觉库&#xff09; OpenCV教程01&#xff1a;图像的操作&#xff08;读取显示保存属性获取和修改像素值&#xff09; OpenCV教程02&#xff1a;图像处理…...

人工智能在招投标领域的运用---监控视频连续性检测

作者&#xff1a;舒城县公共交易中心 zhu_min726126.com 原创&#xff0c;转载请注明出处。 摘要 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其在各个领域的应用日益广泛。本文旨在探讨人工智能在招投标领域的运营&#xff0c;重点介绍AI对视频完整…...

加装德国进口高精度主轴 智能手机壳「高质量高效率」钻孔铣槽

在当前高度智能化的社会背景下&#xff0c;智能手机早已成为人们生活、工作的必备品&#xff0c;智能手机壳作市场需求量巨大。智能手机壳的加工过程涉及多个环节&#xff0c;包括钻孔和铣槽等。钻孔要求精度高、孔位准确&#xff0c;而铣槽则需要保证槽位规整、深度适宜。这些…...

Java Stream API 常用操作技巧

Java 8 引入的 Stream API 为集合操作提供了一种声明式编程模型&#xff0c;极大地简化了数据处理的复杂性。本文将介绍 Java Stream API 的几种常用操作方式&#xff0c;帮助开发者更高效地处理集合数据。 1. 过滤&#xff08;Filtering&#xff09; 过滤是选择集合中满足特…...

SwiftData 模型对象的多个实例在 SwiftUI 中不能及时同步的解决

概览 我们已经知道,用 CoreData 在背后默默支持的 SwiftUI 视图在使用 @FetchRequest 来查询托管对象集合时,若查询结果中的托管对象在别处被改变将不会在 FetchedResults 中得到及时的刷新。 那么这一“囧境”在 SwiftData 里是否也会“卷土重来”呢?空说无益,就让我们在…...

Android 系统网络、时间服务器配置修改

1.修改wifi 是否可用的检测地址&#xff1a; 由于编译的源码用的是谷歌的检测url,国内访问不了&#xff0c;系统会认为wifi网络受限&#xff0c;所以改成国内的地址 adb shell settings delete global captive_portal_https_urladb shell settings delete global captive_por…...

类和对象深入理解

目录 static成员概念静态成员变量面试题补充代码1代码2代码3如何访问private中的成员变量 静态成员函数静态成员函数没有this指针 特性 友元友元函数友元类 内部类特性1特性2 匿名对象拷贝对象时的一些编译器优化 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接…...

在postgres数据库中的几个简单用法

1、例如表中coord_str的字段数据是121.12334 31.3435这样的字符串&#xff0c;如何将对应的数据转换成geometry数据&#xff0c;实现如下 UPDATE coordinates SET geom ST_GeomFromText(POINT( || split_part(coord_str, , 1) || || split_part(coord_str, , 2) || ), 43…...

SQLServer Manager Studio扩展开发从入门到弃坑

Visualstudio的已经开发好了&#xff0c;可这个就是不行&#xff0c;直接运行点这些按钮加载失败&#xff0c;而我直接不调试模式&#xff0c;则直接什么都没有&#xff0c;调试 发现是根本没触发逻辑的。 文档资料太少&#xff0c; 我换了几个ssms.exe都不行&#xff0c;18-20…...

ComfyUI预处理器ControlNet简单介绍与使用(附件工作流)

简介 ControlNet 是一个很强的插件&#xff0c;提供了很多种图片的控制方式&#xff0c;有的可以控制画面的结构&#xff0c;有的可以控制人物的姿势&#xff0c;还有的可以控制图片的画风&#xff0c;这对于提高AI绘画的质量特别有用。接下来就演示几种热门常用的控制方式 1…...

【篇三】在vue3上实现阿里云oss文件直传

之前写了两篇关于文件上传的文章 【篇一】使用springbootvue实现阿里云oss上传 【篇二】使用springbootvue实现阿里云oss文件直传&#xff0c;解决大文件分片上传问题 今天介绍一下在vue3中实现阿里云oss文件直传&#xff0c;主要是基于篇二中的源码进行修改&#xff0c;看具体…...

OceanBase v4.2 特性解析:对Json与Xml的扩展支持

1. 背景 OceanBase的Oracle模式当前已实现对XMLType类型的支持&#xff0c;不仅包含了基本的构造、查询、更新以及格式转换功能&#xff0c;还支持使用Xpath查询从XML数据中提取特定值。在V 4.2.2 版本中&#xff0c;我们进一步扩展了Oracle模式下对XMLType的支持&#xff0c;…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

WebRTC从入门到实践 - 零基础教程

WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC&#xff1f; WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个支持网页浏览器进行实时语音…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

git: early EOF

macOS报错&#xff1a; Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...