Vue3.js“非原始值”响应式实现基本原理笔记(二)
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~
作者:前端小王hs
阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主
此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来
书籍:《Vue.js设计与实现》 作者:霍春阳
本篇博文将在书第5.1节至5.4节的基础上进一步总结所提到的基础概念,附加了测试的代码运行示例,方便正在学习Vue3或想分析Vue3源码的朋友快速阅读
如有帮助,不胜荣幸
前文:
Vue3.js“非原始值”响应式实现基本原理笔记(一)
如何代理Object
“读取”是一个很宽泛的概念(原文)
在之前的笔记中,只简单的讨论了如obj.foo这般获取对象属性值的读取,但读取有很多种,例如下面几种:
- 访问属性:obj.foo
- 判断:key in obj
- 遍历:for (const key in obj)
- …
这一章的内容,就是针对这几种不同的读取,以及其他的常见行为如删除等进行拦截
实现的逻辑主要是看操作符对应的拦截函数
在书中是通过查阅ECMA规范,明确操作符运行逻辑,进而找到操作符的运算结果是调用什么抽象方法,然后通过这个抽象方法找到对应的内部方法,进而对比Vue3.js“非原始值”响应式实现基本原理笔记(一)提到的Proxy内部方法表,选取对应的方法拦截
拦截 in
下面仅以in操作符为例,我们来看一下书中是如何逐步找到拦截方法的
在ECMA-262规范的13.10.1(原文),找到in操作符的运行时逻辑:
- 让 lref 的值为 RelationalExpression 的执行结果。
- 让 lval 的值为 ? GetValue(lref)。
- 让 rref 的值为 ShiftExpression 的执行结果。
- 让 rval 的值为 ? GetValue(rref)。
- 如果 Type(rval) 不是对象,则抛出 TypeError 异常。
- 返回 ? HasProperty(rval, ?
ToPropertyKey(lval))。
关键是第6步,出现了HasProperty(),然后在在ECMA-262规范的7.3.11(原文)找到关于这个方法的逻辑:
- 断言:Type(O) 是 Object。
- 断言:IsPropertyKey§ 是 true。
- 返回 ? O.[[HasProperty]] §。
可以发现这个内部方法[[HasProperty]],然后在表中找到对应的拦截函数——has,如下图所示:

然后就可以在Proxy的handler中使用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的运行时逻辑为例):
RelationalExpression:关系表达式,例如<、>、=、in、instanceof、<=、>=等操作符,在这里指的操作符in的左侧的表达式**ShiftExpression**:位移操作的表达式,操作符in的右侧的表达式lref和lval:RelationalExpression会生成一个引用lref,然后通过GetValue(lref)得到lvalrref和lref:逻辑同上
拦截 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是传入target和key,代码如下:
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_KEY,iterate的意思是重复
为什么传入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去考虑的不同情况
总结
这篇笔记主要复习了不同读取情况下的响应式实现:
- 如何拦截in操作符
- 如何拦截for…in操作符
- 当新增、修改和删除时如何实现响应式
相关文章:
Vue3.js“非原始值”响应式实现基本原理笔记(二)
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~ 作者:前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…...
论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型
论文全称:PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题:如何在检索增强式问答(ReQA)任务中,利用大型语言模型…...
网络状态的智能感知:WebKit 支持 Network Information API 深度解析
网络状态的智能感知:WebKit 支持 Network Information API 深度解析 在现代 Web 应用中,理解用户的网络连接状态对于提供适应性体验至关重要。Network Information API,一个新兴的 Web API,允许 Web 应用访问设备的网络信息&…...
Vue3基础知识:组合式API中的provide和inject,他们作用是什么?如何使用?以及案例演示
1.provide和inject相较于父子传递的不同在于provide,inject可以用于跨层级通信(通俗易懂的讲就是可以实现爷孙之间的直接信息传递)。 1.跨层级传递数据 1.在顶层组件通过provide函数提供数据 2.底层组件通过inject函数获取数据 演示一:跨…...
Transformer自注意力机制(Self-Attention)模型
上一篇我们介绍了transform专题一:Seq2seq model,也知道了transfrom属于seq2seq模型,这一排篇咱们接着介绍另外几种seq2seq架构的模型。)RNN(循环神经网络)CNN(卷积神经网络)&…...
【计算机体系结构】缓存的false sharing
在介绍缓存的false sharing之前,本文先介绍一下多核系统中缓存一致性是如何维护的。 目前主流的多核系统中的缓存一致性协议是MESI协议及其衍生协议。 MESI协议 MESI协议的4种状态 MESI协议有4种状态。MESI是4种状态的首字母缩写,缓存行的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 # 创建虚拟环境,已有可跳过…...
docker 设置代理,通过代理服务器拉取镜像
docker 拉取目标镜像需要通过代理服务器进行时,可以通过为 docker 配置全局代理来实现。 注:Linux 上通过临时命令 export HTTP_PROXY 设置的代理,对 curl 这些有用,但是对 docker pull 不起作用。 示例 假设您的代理服务器地址是…...
OpenCV教程02:图像处理系统1.0(翻转+形态学+滤波+缩放+旋转)
-------------OpenCV教程集合------------- Python教程99:一起来初识OpenCV(一个跨平台的计算机视觉库) OpenCV教程01:图像的操作(读取显示保存属性获取和修改像素值) OpenCV教程02:图像处理…...
人工智能在招投标领域的运用---监控视频连续性检测
作者:舒城县公共交易中心 zhu_min726126.com 原创,转载请注明出处。 摘要 随着人工智能(AI)技术的飞速发展,其在各个领域的应用日益广泛。本文旨在探讨人工智能在招投标领域的运营,重点介绍AI对视频完整…...
加装德国进口高精度主轴 智能手机壳「高质量高效率」钻孔铣槽
在当前高度智能化的社会背景下,智能手机早已成为人们生活、工作的必备品,智能手机壳作市场需求量巨大。智能手机壳的加工过程涉及多个环节,包括钻孔和铣槽等。钻孔要求精度高、孔位准确,而铣槽则需要保证槽位规整、深度适宜。这些…...
Java Stream API 常用操作技巧
Java 8 引入的 Stream API 为集合操作提供了一种声明式编程模型,极大地简化了数据处理的复杂性。本文将介绍 Java Stream API 的几种常用操作方式,帮助开发者更高效地处理集合数据。 1. 过滤(Filtering) 过滤是选择集合中满足特…...
SwiftData 模型对象的多个实例在 SwiftUI 中不能及时同步的解决
概览 我们已经知道,用 CoreData 在背后默默支持的 SwiftUI 视图在使用 @FetchRequest 来查询托管对象集合时,若查询结果中的托管对象在别处被改变将不会在 FetchedResults 中得到及时的刷新。 那么这一“囧境”在 SwiftData 里是否也会“卷土重来”呢?空说无益,就让我们在…...
Android 系统网络、时间服务器配置修改
1.修改wifi 是否可用的检测地址: 由于编译的源码用的是谷歌的检测url,国内访问不了,系统会认为wifi网络受限,所以改成国内的地址 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这样的字符串,如何将对应的数据转换成geometry数据,实现如下 UPDATE coordinates SET geom ST_GeomFromText(POINT( || split_part(coord_str, , 1) || || split_part(coord_str, , 2) || ), 43…...
SQLServer Manager Studio扩展开发从入门到弃坑
Visualstudio的已经开发好了,可这个就是不行,直接运行点这些按钮加载失败,而我直接不调试模式,则直接什么都没有,调试 发现是根本没触发逻辑的。 文档资料太少, 我换了几个ssms.exe都不行,18-20…...
ComfyUI预处理器ControlNet简单介绍与使用(附件工作流)
简介 ControlNet 是一个很强的插件,提供了很多种图片的控制方式,有的可以控制画面的结构,有的可以控制人物的姿势,还有的可以控制图片的画风,这对于提高AI绘画的质量特别有用。接下来就演示几种热门常用的控制方式 1…...
【篇三】在vue3上实现阿里云oss文件直传
之前写了两篇关于文件上传的文章 【篇一】使用springbootvue实现阿里云oss上传 【篇二】使用springbootvue实现阿里云oss文件直传,解决大文件分片上传问题 今天介绍一下在vue3中实现阿里云oss文件直传,主要是基于篇二中的源码进行修改,看具体…...
OceanBase v4.2 特性解析:对Json与Xml的扩展支持
1. 背景 OceanBase的Oracle模式当前已实现对XMLType类型的支持,不仅包含了基本的构造、查询、更新以及格式转换功能,还支持使用Xpath查询从XML数据中提取特定值。在V 4.2.2 版本中,我们进一步扩展了Oracle模式下对XMLType的支持,…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...
