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

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 将不会再存在新增属性时失去响应性的问题

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的程序性: 一套固定的&#xff0c;不会发生变化的执行流程 1 &#xff09;没有响应的数据 // 定义商品对象 const product {price: 10,quantity: 2 }// 总价格 let total product.price * product.quantity console.log(总价格&#xff1a;${total}) //…...

Jetpack Compose开发一个Android WiFi导航应用

在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中&#xff0c;我介绍了如何用Android来测量WiFi信号&#xff0c;上传到服务器进行分析后&#xff0c;生成室内不同地方的WiFi指纹&#xff0c;从而帮助进行室内导航。当时我是用的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多线程编程中&#xff0c;锁&#xff08;lock&#xff09;和解锁&#xff08;unlock&#xff09;通常用于管理共享资源的访问&#xff0c;以防止多个线程同时对资源进行修改&#xff0c;从而避免竞态条件&#xff08;Race Condition&#xff09;和数据不一致性问题。C标准库…...

【Mypy】超级实用的python高级库!

今天&#xff0c;我很兴奋地向大家介绍一个神奇的Python库&#xff1a;Mypy。这个库是Python世界中的一颗璀璨明星&#xff0c;提供了静态类型检查的强大功能&#xff0c;极大地增强了Python这门动态类型语言的健壮性和可维护性。我们将深入探索Mypy的多个方面&#xff0c;并通…...

【Python基础】循环语句

文章目录 [toc]什么是循环Python中的循环方式while循环格式示例 什么是循环 程序中需要重复执行的代码&#xff0c;可以通过循环实现比如和女朋友道歉&#xff0c;或一万遍“宝宝&#xff0c;我错了”&#xff0c;在没有学习循环之前&#xff0c;我们只能通过如下方式实现 pr…...

【面试】广告优化

a1&#xff1a;点击率公式是什么&#xff1f;点击率低的原因是什么&#xff1f; 点击率点击/曝光&#xff0c;点击率低的原因主要有两点&#xff1a;一是创意不吸引人&#xff1b;二是目标受众不准确/定向过宽不精确&#xff0c;广告曝光给了对产品不感兴趣用户 a2&#xff1a;…...

RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 RabbitMQ时空之旅&#xff1a;rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用&#xff1a;如何在 RabbitMQ 中启用消息时间戳&…...

AD9361 Evaluation Software配置脚本转换工具

最近在玩一个开源的AD9361项目&#xff0c;AD9361采用纯逻辑配置&#xff0c;不需要ARM或者MicroBlaze。其中&#xff0c;先是用AD9361 Evaluation Software生成配置脚本&#xff0c;再转换成ad9361_lut.v。 在网上查了一圈&#xff0c;有个转换工具叫bit_converter&#xff0…...

Centos7 配置Git

随笔记录 目录 1&#xff0c; 新建用户 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)&#xff0c;这是一组经过预训练和微调的生成文本模型&#xff0c;参…...

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() 命名空间 &#xff1a;UnityEngine.EventSystems 官方描述&#xff1a; public bool IsPointerOverGameObject(); public bool IsPointerOverGameObject(int pointerId); //触摸屏时需要的参数&#xff…...

基于QTreeWidget实现带Checkbox的多级组织结构选择树

基于QTreeWidget实现带Checkbox的多级组织结构选择树 采用基于QWidgetMingw实现的原生的组织结构树 通过QTreeWidget控件实现的带Checkbox多级组织结构树。 Qt相关系列文章&#xff1a; 一、Qt实现的聊天画面消息气泡 二、基于QTreeWidget实现多级组织结构 三、基于QTreeWidget…...

探索 Vim:一个强大的文本编辑器

引言&#xff1a; Vim&#xff08;Vi IMproved&#xff09;是一款备受推崇的文本编辑器&#xff0c;拥有强大的功能和高度可定制性&#xff0c;提供丰富的编辑和编程体验。本文将探讨 Vim 的基本概念、使用技巧以及为用户带来的独特优势。 简介和发展 1. Vim 的简介和历史 V…...

K8S(十)—容器探针

这里写目录标题 容器探针&#xff08;probe&#xff09;检查机制探测结果探测类型何时该使用存活态探针?何时该使用就绪态探针?何时该使用启动探针&#xff1f; 使用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时&#xff0c;发现一个问题&#xff0c;启动以前的项目突然报子容器启动异常。 异常信息如下&#xff1a; 严重: 子容器启动失败 java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: 无法启动组件[org.apache.…...

JAVA序列化(创建可复用的 Java 对象)

JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象&#xff0c;但一般情况下&#xff0c;只有当 JVM 处于运行时&#xff0c;这些对象才可能存在&#xff0c;即&#xff0c;这些对象的生命周期不…...

Hermes Agent 框架对接 Taotoken 自定义提供方的配置要点与排错

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Hermes Agent 框架对接 Taotoken 自定义提供方的配置要点与排错 基础教程类&#xff0c;针对希望将 Hermes Agent 连接到 Taotoken…...

OpenCart安全审计实战:静态代码扫描与核心漏洞修复指南

1. 项目概述与核心价值最近在整理一个基于OpenCart的电商项目时&#xff0c;客户提出了一个非常具体且关键的需求&#xff1a;需要对整个系统的安全性进行一次全面的审计。这不仅仅是运行一个自动化扫描工具那么简单&#xff0c;客户希望我们能深入代码层面&#xff0c;检查是否…...

Illustrator脚本自动化终极指南:如何节省设计师90%重复工作时间

Illustrator脚本自动化终极指南&#xff1a;如何节省设计师90%重复工作时间 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts Adobe Illustrator脚本自动化是每个设计师都应该掌握的生…...

SpringBoot+Vue的牙科诊所预约平台毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于Spring Boot与Vue框架的牙科诊所预约平台以解决传统医疗预约模式中存在的信息不对称问题和资源分配效率低下问题。随着数字化医疗技术的快…...

开源短剧源码|短剧小程序源码短剧App源码双端适配,即开即用

在当下这个注意力稀缺的时代&#xff0c;短剧以其“爽点密集、节奏明快、情感代入强”的特点&#xff0c;迅速抢占了海量用户的碎片化时间。无论是国内的微信/抖音小程序生态&#xff0c;还是出海的短剧App市场&#xff0c;都呈现出爆发式的增长态势。然而&#xff0c;对于想要…...

调幅无线传数据:避开这些坑,你的7kHz方波才能传得更远更稳

调幅无线传数据&#xff1a;避开这些坑&#xff0c;你的7kHz方波才能传得更远更稳 在业余无线电和嵌入式通信领域&#xff0c;调幅&#xff08;AM&#xff09;无线传输一直是低成本解决方案的热门选择。但许多工程师在尝试用7kHz方波调制高频载波时&#xff0c;总会遇到信号失真…...

[STM32U3] 【每周分享】【STM32U385RG 测评】+调试串口通讯,字符串打印

接着上一回&#xff0c;这会进行串口打印实验 一、查询原理图&#xff0c;找到我们需要配置的串口 如上图&#xff1a;PA9、PA10、USART1 二、按流程打开IDE软件&#xff0c;建立新的工程文件。 配置如下&#xff1a;debug、RCC、USART1 配置完成后就可以生成代码了 三、代…...

cPanel三连漏洞CVE-2026-29201/29202/29203深度解析:150万服务器面临全面接管危机

一、事件引言&#xff1a;2026年主机行业最大安全地震 2026年5月8日&#xff0c;全球市场份额第一的服务器管理面板cPanel & WHM 发布紧急安全公告&#xff0c;一次性披露三个高危安全漏洞&#xff08;CVE-2026-29201/29202/29203&#xff09;。这组被安全界称为"cPa…...

Hydrin 1 ([Arg8, Gly10, Lys11, Arg12]-Vasotocin)

一、基础信息多肽名称&#xff1a;Hydrin 1&#xff0c;加压催产素变体 [Arg8, Gly10, Lys11, Arg12]-Vasotocin 三字母序列&#xff1a;Cys-Tyr-Ile-Gln-Asn-Cys-Pro-Arg-Gly-Gly-Lys-Arg 单字母序列&#xff1a;CYIQNCPRG GKR 氨基酸数目&#xff1a;12 aa 结构特征&#xff…...

【必收藏】2026年大模型学习全指南|小白程序员入门捷径,抓住百万年薪红利

2026年的AI行业&#xff0c;机遇早已从风口走向实锤——应用层依旧是那片肉眼可见的黄金赛道&#xff01;从大厂技术布局到招聘市场风向标&#xff0c;所有信号都在一致指向&#xff1a;大模型应用开发&#xff0c;已然成为程序员突破职业瓶颈、实现薪资跃升的核心赛道。 字节跳…...