Vue3源码梳理:响应式系统的前世今生
响应性数据的前世
- js的程序性: 一套固定的,不会发生变化的执行流程
1 )没有响应的数据
// 定义商品对象
const product = {price: 10,quantity: 2
}// 总价格
let total = product.price * product.quantity
console.log(`总价格:${total}`) // 20// 修改商品的数量
product.quantity = 5
console.log(`总价格:${total}`) // 20
- 这是一段非常普通的js程序,当最后
product.quantity发生改变的时候,最终结果并没有发生变化 - 这里,当商品数量发生变化,总价格也会发生变化是我们的期望
- 由于js程序性的约束,我们只能得到20,我们想让程序变得更智能
2 )进一步改造
// 定义商品对象
const product = {price: 10,quantity: 2
}// 总价格
let total = 0// 定义一个 effect 函数
const effect = () => {total = product.price * product.quantity // 访问属性,这里是 getter行为
}effect()
console.log(`总价格:${total}`) // 20// 修改商品的数量
product.quantity = 5 // 修改属性,这里是 setter 行为effect() // 注意这里
console.log(`总价格:${total}`) // 50
- 这里,封装了一个effect方法,这个方法是重新计算 total 的方法
- 当
product.quantity数据发生改变的时候,手动调用了一次 effect 方法 - 以上的方式是每次手动触发 effect 方法进行一次 类似 getter 操作
- 这样手动操作,是比较麻烦的
- 为此,js中的API可以有效解决这个问题
响应式数据的今生
1 )关于响应性数据
- 响应数据:是指影响视图变化的数据
2 ) vue2核心响应式API Object.defineProperty() 方法
let quantity = 2
const product = {price: 10,quantity
}// 总价格
let total = 0// 计算总价格函数
const effect = () => {total = product.price * product.quantity
}effect()
console.log(`总价格:${total}`) // 20// 响应式变化
Object.defineProperty(product, 'quantity', {set(newVal) {console.log('setter')quantity = newValeffect()},get() {console.log('getter')return quantity // 这里的变量是暴露在最外面的,不是很好}
})
- 这样可以在指定对象上,指定属性上的 getter 和 setter 行为,以此来触发effect(更新程序)
- 这样来说,相对更智能了
3 ) Obeject.defineProperty() 在设计上的缺陷
- 存在一个致命缺陷:vue官网/深入响应式原理/检测变化的注意事项
- 由于js的限制,vue不能检测数组和对象变化
代码示例,如下
<template><div><ul><li v-for="(val, key, index) in obj" :key="index">{{ key }} --- {{ val }}</li></ul><button @click="addObjKey">为对象增加属性</button><div> ---------------- </div><ul><li v-for="(item, index) in arr" :key="index">{{ item }} --- {{ index }}</li></ul><button @click="addArrItem">为数组增加元素</button></div>
</template>
<script>export default {name: 'App',data() {return {obj: {name: '张三',age: 30},arr: ['张三', '李四']}},methods: {addObjKey() {this.obj.gender = '男'console.log(this.obj)},addArrItem() {this.arr[2] = '王五'console.log(this.arr)}}}
</script>
- 上面两个按钮点击后,数据会更新,但是页面视图不会更新
- 当对象新增一个没有在data中声明的属性时,新增的属性不是响应式的
- 当为数组通过下标形式新增一个元素时,新增的元素不是响应式的
- why?
- Object.defineProperty 只能监听指定对象,指定属性的 getter 和 setter
- js限制是指:没有办法知道为某一个对象新增了某一个属性这类行为,新增属性会失去响应性
4) Vue3中的 Proxy
-
文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
-
语法
const p = new Proxy(target, handler)- target 表示proxy包装的目标对象,可以是任何对象: 原生数组,函数,甚至另一个对象
- p 是 proxy的实例,是代理对象
- handler 是一个对象,可以在这个对象上指定getter和setter
代码改造,示例如下
// 定义商品对象
const product = {price: 10,quantity: 2
}// 生成代理对象, 注意事项:使用时不能使用被代理对象(原对象),而应该使用代理对象
// proxy 代理的是整个对象,而非某个对象的某个属性
const proxyProduct = new Proxy(product, {set(target, key, newVal, receiver) {// console.log('setter')target[key] = newVal// 这里触发 effect 重新计算effect()return true},get(target, key, receiver) {// console.log('getter')return target[key]}
})// 总价格
let total = 0// 定义一个 effect 函数
const effect = () => {total = proxyProduct.price * proxyProduct.quantity // 访问属性,这里是 getter行为
}effect()
console.log(`总价格:${total}`) // 20// 修改商品的数量, 注意这里是修改的代理对象的值,而非被代理对象的值
proxyProduct.quantity = 5 // 修改属性,这里是 setter 行为effect()
console.log(`总价格:${total}`) // 50
- 通过修改代理对象的值,来让被代理对象同步发生变化
- 这里使用 proxy 完成了 和 Object.defineProperty一样的效果
- 总结:
- proxy:
- Proxy 将一个对象 (被代理对象), 得到一个新的对象 (代理对象), 同时拥有被代理对象中所有的属性
- 当想要修改对象的指定属性时,我们使用 代理对象 进行修改
- 代理对象的任何一个属性都可以触发 handler 的getter和setter
- Object.defineProperty
- 该API为指定对象的指定属性 设置 属性描述符
- 当想要修改对象的指定属性时,可以使用原对象进行修改
- 通过属性描述符,只有 被监听 的指定属性,才可以触发 getter 和 setter
- 所以,当 vue3 通过 Proxy 实现响应性核心 API 之后, vue 将不会再存在新增属性时失去响应性的问题
- proxy:
5 ) proxy的最佳合伙API: Reflect, 拦截js对象操作
-
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
-
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
const obj = { name: '张三' } Reflect.get(obj, 'name') // '张三' -
Reflect.get(target, propertyKey[, receiver])
- 可以看到,这个 API 有三个参数
- target 需要取值的目标对象
- propertyKey 需要获取的值的键值
- receiver 如果target对象中指定了getter,receiver则为getter调用时的this值
测试代码如下:
// p1 对象
const p1 = {lastName: '张',firstName: '三',get fullName() {return this.lastName + this.firstName}
}// p2 对象
const p2 = {lastName: '李',firstName: '四',get fullName() {return this.lastName + this.firstName}
}// 测试
console.log(p1.fullName) // 张三
console.log(Reflect.get(p1, 'fullName')) // 张三
console.log(Reflect.get(p1, 'fullName', p2)) // 李四 这里,改变了getter的this指向,this指向了 p2, 所以 getter中获取的是 p2的fullName属性console.log(p2.fullName) // 李四
使用 proxy 和 Reflect 一起使用
// p1 对象
const p1 = {lastName: '张',firstName: '三',get fullName() {return this.lastName + this.firstName}
}const proxy = new Proxy(p1, {get(target, key, receiver) {console.log('getter')return target[key]}
})console.log(proxy.fullName) // 这里进行一次getter操作,会执行一次
- 上述代码只会触发一次getter, 因为其中的this指向是target,也就是原对象p1
- 但是,我们的理解,如果做任意的取值都会触发一次getter, 也就是 访问fullName的时候会触发一次getter, 但是fullName里面也有两次getter
- 这时候,我们想要触发三次getter 如何修改呢
// p1 对象
const p1 = {lastName: '张',firstName: '三',get fullName() {return this.lastName + this.firstName}
}const proxy = new Proxy(p1, {get(target, key, receiver) {console.log('getter', key)// return target[key]return Reflect.get(target, key, receiver) // 注意,修改这里}
})console.log(proxy.fullName) // 这里进行一次getter操作,会执行一次
- 这时候 proxy.fullName 会触发 三次 getter的行为
- 先输出:fullName 的getter
- 再输出:lastName 的getter
- 最后输出:firstName 的getter
- 某些场景下,使用
return target[key]会存在bug, - 请使用
return Reflect.get(target, key, receiver)代替
相关文章:
Vue3源码梳理:响应式系统的前世今生
响应性数据的前世 js的程序性: 一套固定的,不会发生变化的执行流程 1 )没有响应的数据 // 定义商品对象 const product {price: 10,quantity: 2 }// 总价格 let total product.price * product.quantity console.log(总价格:${total}) //…...
Jetpack Compose开发一个Android WiFi导航应用
在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中,我介绍了如何用Android来测量WiFi信号,上传到服务器进行分析后,生成室内不同地方的WiFi指纹,从而帮助进行室内导航。当时我是用的HTML5的技术来快速开发一个An…...
【Mode Management】ComM详细介绍
目录 1. Introduction and functional overview 2.Dependencies to other modules 3.Functional specification 3.1 Partial Network Cluster Management 3.2 ComM channel state machine 3.2.1 Behaviour in state COMM_NO_COMMUNICATION 3.2.1.1 COMM_NO_COM_NO_PENDI…...
【C++多线程编程】(二)之详解锁(lock)和解锁(unlock)
在C多线程编程中,锁(lock)和解锁(unlock)通常用于管理共享资源的访问,以防止多个线程同时对资源进行修改,从而避免竞态条件(Race Condition)和数据不一致性问题。C标准库…...
【Mypy】超级实用的python高级库!
今天,我很兴奋地向大家介绍一个神奇的Python库:Mypy。这个库是Python世界中的一颗璀璨明星,提供了静态类型检查的强大功能,极大地增强了Python这门动态类型语言的健壮性和可维护性。我们将深入探索Mypy的多个方面,并通…...
【Python基础】循环语句
文章目录 [toc]什么是循环Python中的循环方式while循环格式示例 什么是循环 程序中需要重复执行的代码,可以通过循环实现比如和女朋友道歉,或一万遍“宝宝,我错了”,在没有学习循环之前,我们只能通过如下方式实现 pr…...
【面试】广告优化
a1:点击率公式是什么?点击率低的原因是什么? 点击率点击/曝光,点击率低的原因主要有两点:一是创意不吸引人;二是目标受众不准确/定向过宽不精确,广告曝光给了对产品不感兴趣用户 a2:…...
RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】
欢迎来到我的博客,代码的世界里,每一行都是一个故事 RabbitMQ时空之旅:rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用:如何在 RabbitMQ 中启用消息时间戳&…...
AD9361 Evaluation Software配置脚本转换工具
最近在玩一个开源的AD9361项目,AD9361采用纯逻辑配置,不需要ARM或者MicroBlaze。其中,先是用AD9361 Evaluation Software生成配置脚本,再转换成ad9361_lut.v。 在网上查了一圈,有个转换工具叫bit_converter࿰…...
Centos7 配置Git
随笔记录 目录 1, 新建用户 2. 给用户设置密码相关操作 3. 为新用户添加sudo 权限 4. 配置Git 4.1 配置Git 4.2 查看id_ras.pub 5, 登录Git 配置SSH 秘钥 6. Centos7 登录Git 7. clone 指定branch到本地 8. 将新代码复制到指定路径 9. 上传指定代码 …...
python工具方法 44 数据仿真生成(粘贴目标切片到背景图像上,数据标签校验)
在深度学习训练中数据是一个很重要的因素,在数据不够时需要我们基于现有的数据进行增强生成新的数据。此外,在某特殊情况,如对某些目标切片数据(例如:石块分割切片)预测效果较差,需要增强其在训练数据中的频率。故此,我们可以将先有数据标注中的目标裁剪出来,作为样本…...
Llama 架构分析
从代码角度进行Llama 架构分析 Llama 架构分析前言Llama 架构分析分词网络主干DecoderLayerAttentionMLP 下游任务因果推理文本分类 Llama 架构分析 前言 Meta 开发并公开发布了 Llama系列大型语言模型 (LLM),这是一组经过预训练和微调的生成文本模型,参…...
vue3前端 md5工具类
工具类 /*** Namespace for hashing and other cryptographic functions* Copyright (c) Andrew Valums* Licensed under the MIT license, http://valums.com/mit-license/*/var V V || {}; V.Security V.Security || {};(function () {// for faster accessvar S V.Secur…...
Unity触摸 射线穿透UI解决
unity API 之EventSystem.current.IsPointerOverGameObject() 命名空间 :UnityEngine.EventSystems 官方描述: public bool IsPointerOverGameObject(); public bool IsPointerOverGameObject(int pointerId); //触摸屏时需要的参数ÿ…...
基于QTreeWidget实现带Checkbox的多级组织结构选择树
基于QTreeWidget实现带Checkbox的多级组织结构选择树 采用基于QWidgetMingw实现的原生的组织结构树 通过QTreeWidget控件实现的带Checkbox多级组织结构树。 Qt相关系列文章: 一、Qt实现的聊天画面消息气泡 二、基于QTreeWidget实现多级组织结构 三、基于QTreeWidget…...
探索 Vim:一个强大的文本编辑器
引言: Vim(Vi IMproved)是一款备受推崇的文本编辑器,拥有强大的功能和高度可定制性,提供丰富的编辑和编程体验。本文将探讨 Vim 的基本概念、使用技巧以及为用户带来的独特优势。 简介和发展 1. Vim 的简介和历史 V…...
K8S(十)—容器探针
这里写目录标题 容器探针(probe)检查机制探测结果探测类型何时该使用存活态探针?何时该使用就绪态探针?何时该使用启动探针? 使用exechttptcpgrpc使用命名端口 使用启动探针保护慢启动容器定义就绪探针配置探针HTTP 探测TCP 探测探针层面的…...
[C错题本]
1.int,short,long都是signed的 但是char可能是signed 也可能是unsigned的——《C Primer》 2.在16位的PC中 char类型占1个字节 int占2个字节 long int占4个字节 float占四个字节 double占八个字节 3.自增运算符和自减运算符即使是在判断条件中使用也会实际生效 int i 1; int…...
tomcat启动异常:子容器启动失败(a child container failed during start)
最近在使用eclipse启动Tomcat时,发现一个问题,启动以前的项目突然报子容器启动异常。 异常信息如下: 严重: 子容器启动失败 java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: 无法启动组件[org.apache.…...
JAVA序列化(创建可复用的 Java 对象)
JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
