当前位置: 首页 > 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;这些对象的生命周期不…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

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

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

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...