vue3 源码解析(3)— computed 计算属性的实现
前言
本文是 vue3 源码分析系列的第三篇文章,主要介绍 vue3 computed 原理。computed 是 vue3 的一个特性,可以根据其他响应式数据创建响应式的计算属性。计算属性的值会根据依赖的数据变化而自动更新,而且具有缓存机制,提高了性能。在这篇文章中,我们将深入探讨 computed 的实现原理,并通过源码分析来理解其工作机制。
computed 的基本用法
在 vue3 中,computed
的基本用法如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>computed</title>
</head>
<body>
<script src="../packages/reactivity/dist/reactivity.global.js"></script>
<script>let { reactive, computed } = VueReactivity;const state = reactive({count: 0});const double = computed(() => state.count * 2);console.log(double.value); // 0setTimeout(() => {state.count++;console.log(double.value); // 2}, 1000);
</script></body>
</html>
在上面的例子中,我们首先创建了一个响应式对象 state,然后我们使用 computed 创建了一个计算属性 double。这个计算属性的值是 state.count 的两倍。当 state.count 的值发生变化时,double 的值也会自动更新。
computed 的实现原理
computed 函数的源码如下:
// 定义一个名为computed的函数,该函数接受一个参数getterOrOptions,这个参数可以是计算属性的getter函数,也可以是包含get和set方法的对象
export function computed<T>(getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {// 定义两个变量getter和setter,分别用于存储计算属性的getter函数和setter函数let getter: ComputedGetter<T>let setter: ComputedSetter<T>// 判断getterOrOptions是否为函数类型,如果是,说明只传入了getter函数const onlyGetter = isFunction(getterOrOptions)if (onlyGetter) {// 如果只传入了getter函数,将getterOrOptions赋值给getter,同时定义setter函数getter = getterOrOptionssetter = __DEV__? () => {warn(`Write operation failed: computed value is readonly`)}: NOOP} else {// 如果传入了包含get和set方法的对象,将get方法赋值给getter,将set方法赋值给settergetter = getterOrOptions.getsetter = getterOrOptions.set}// 创建一个新的ComputedRefImpl实例,并返回。这个实例接收getter、setter和一个布尔值参数return new ComputedRefImpl(getter,setter,isFunction(getterOrOptions) || !getterOrOptions.set) as any
}
computed 函数接受一个函数或一个对象作为参数,如果是函数,就是计算属性的 getter 函数。如果是对象,就是包含 get 和 set 函数的选项对象。computed 函数会返回一个 ComputedRefImpl 的实例。ComputedRefImpl 的构造函数如下:
ComputedRefImpl
ComputedRefImpl 函数的源码如下:
class ComputedRefImpl<T> {// 定义一个私有变量 _dirty,表示是否需要重新计算值。初始化为 true 表示需要重新计算private _dirty = true// 定义一个公共的 reactive effect,用于观察和响应值的变化public readonly effect: ReactiveEffect<T>// 定义两个公共的只读属性,用于标识这个引用是否只读和是否是响应式的引用public readonly __v_isReadonly: booleanpublic readonly __v_isRef = trueconstructor(getter: ComputedGetter<T>,private readonly _setter: ComputedSetter<T>,isReadonly: boolean) {// 创建一个 reactive effect,当值发生变化时触发。如果值没有变化则不触发。 this.effect = effect(getter, {lazy: true, // 懒加载,只有当需要时才执行scheduler: () => { // 调度器函数if (!this._dirty) {this._dirty = true // 重置 _dirty 为 true,表示需要重新计算}}})this.effect.computed = this}// 定义一个 getter 方法,返回当前的值。get value() {if (this._dirty) { // 如果 _dirty 为 true,表示需要被重新计算this._value = this.effect() // 调用 effect 方法来计算值this._dirty = false // 设置 _dirty 为 false,表示值已经被计算过}return this._value}// 定义一个 setter 方法,设置新的值并调用传入的 setter 方法来处理这个新值set value(newValue: T) {this._setter(newValue)}
}
ComputedRefImpl 构造函数内部创建了一个 effect 副作用函数,函数传入了 getter 函数,和一个选项对象。选项对象中有两个属性,一个是 lazy,表示副作用函数是否延迟执行,一个是 scheduler,表示副作用函数的调度器,用于在依赖的数据变化时触发副作用函数的重新执行。
effect
effect 函数的源码如下:
function effect<T = any>(fn: () => T, options?: ReactiveEffectOptions): ReactiveEffect<T> {// 创建一个新的effect函数,调用run方法执行原始的函数const _effect = new ReactiveEffect(fn, NOOP, () => {if (_effect.dirty) {_effect.run()}})// 如果options中没有设置lazy为true,就立即执行effect函数if (!options || !options.lazy) {_effect.run()}const runner = _effect.run.bind(_effect) as ReactiveEffectRunnerrunner.effect = _effectreturn runner
}
-
由于在 ComputedRefImpl 类中定义 effect 函数时,传入的 lazy 为 true 函数并不会自动执行。
-
当我们访问 double.value 时会触发 ComputedRefImpl 类里面的 get 这个函数,执行 effect 函数。在此过程中会建立属性依赖关系。
-
执行完之后,将 this._dirty 设置为 false,这一步在后面计算属性是否会重新执行起到关键作用。
scheduler
scheduler 函数通常用于控制计算属性的更新调度。 函数提供了一种额外的调度控制机制,使你能够更好地控制计算属性的更新时机和逻辑。那么 scheduler 是如何被执行的:
function trigger (target, type, key, newValue?, oldValue?) {// 此处省略部分代码for (const effect of effects) {// computed 表示这是一个计算属性if (effect.computed) {// 执行调度器函数// 重新修改 dirty 的值,标记该计算属性为“脏”状态,表示其值已被修改effect.options.scheduler()} else {effect()}}
}
-
对于 effects 中的每一个 effect,如果它是一个计算属性,那么会调用它的调度器函数来进行更新。否则,直接调用 effect() 来运行副作用。
-
scheduler 函数,用来在依赖项变化时改变 _dirty 标志位,表示计算属性需要重新计算。
-
当我们再次访问 double.value 时,由于 this._dirty = true 又会重新执行 get 这个函数,然后更新 double 的值。
通过这个机制使得 vue3 能够精确地控制哪些副作用需要在响应式数据变化时更新,从而提高了性能。
总结
通过对 computed
的源码分析,我们可以看到 vue3 如何实现计算属性的。当我们创建一个计算属性时,vue 会收集这个计算属性的所有依赖项。然后,当这些依赖项发生变化时,vue 会重新计算这个计算属性的值。并通过 _dirty
这个标志位来判断是否需要执行副作用函数并更新 _value
的值。这种机制使得我们可以方便地创建基于其他响应式依赖项的计算属性。
相关文章:
vue3 源码解析(3)— computed 计算属性的实现
前言 本文是 vue3 源码分析系列的第三篇文章,主要介绍 vue3 computed 原理。computed 是 vue3 的一个特性,可以根据其他响应式数据创建响应式的计算属性。计算属性的值会根据依赖的数据变化而自动更新,而且具有缓存机制,提高了性…...

Alibaba-> EasyExcel 整理3
1 导入依赖 <!-- easyExcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version >3.2.1</version><exclusions><exclusion><artifactId>poi-ooxml-schemas</art…...

创建组-RibbonGroup
使用实例如下: 1、main中: #include "QRibbonDemo.h" #include <QtWidgets/QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setStyle(new RibbonStyle()); a.setApplicationName(&quo…...
面试题目1
文章目录 1、安装系统的方法2、总线型3、OSL参考模型(网络七层模型)4、计算机系统的组成5、计算机硬件 1、安装系统的方法 U盘安装 硬盘安装 刻光盘安装 PE系统中安装 网络安装 2、总线型 所有设备都连接到公共总线上,结点间使用广播通信方…...

考古学家 - 华为OD统一考试
OD统一考试 分值: 200分 题解: Java / Python / C++ 题目描述 有一个考古学家发现一个石碑,但是很可惜发现时其已经断成多段。 原地发现N个断口整齐的石碑碎片,为了破解石碑内容,考古学家希望有程序能帮忙计算复原后的石碑文字组合数,你能帮忙吗? 备注: 如果存在石碑…...
Linux服务器安全配置基线
基线要求: 安全类别 检查项 检查要求 检查步骤 备注 账户及口令安全 1.1 检查是否设置口令生存周期 应配置口令生存周期,密码最长使用期限应小于等于90天,密码最短使用期限应非0。 执行:cat /etc/login.defs,检查是否配置了以下参数。 PASS_MAX_DAYS 配置项决定密码最长使…...
Kafka外网访问
文章目录 一、背景二、需求三、调研四、配置内外网访问 一、背景 kafka机器只有内网IP,没有绑定外网网卡,但是可以在防火墙或通过其他有公网IP的设备上进行公网IP端口的映射。 二、需求 kafka集群端口映射后,可以通过外网IP端口进行数据生…...

Vant2组件库van-list+Toast下拉加载滚动条回顶问题
目录 List 列表 Toast 轻提示 解决方案 1、不使用 Toast 的 加载提示 2、修改调整 pointer-event 属性值 3、判断是否为第一次加载再使用 背景 : 移动端项目 开发时,有数据长列表展示的场景需求,此时就用到了 Vant2 组件库里面的 <v…...

使用 C++/WinRT 创作 API
如果 API 位于 Windows 命名空间中 这是你使用 Windows 运行时 API 最常见的情况。 对于元数据中定义的 Windows 命名空间中的每个类型,C/WinRT 都定义了 C 友好等效项(称为投影类型 )。 投影类型具有与 Windows 类型相同的完全限定名称&…...
C#写windows服务,实现把检测软件崩溃工具写成服务 自动运行
一、打开Visual Studio,创建项目->Windows 服务(.NET Framework) 二、点击Service.cs 点击切换到代码视图 static Timer Timer; private Thread monitorThread; private static string logFilePath; private static Process winFormsProcess; public Service1(…...
QT中线程的退出分析
QT中线程的退出分析 前言开发环境代码的整改第一次修改第一次修改引起的问题问题原因分析解决方法第二次修改前言 软件实际开发过程中有好几处要实现这么一个功能:PC端软件“应用程序重启” ,本来这是一段比较简单的事情,但是因为重启软件报通信异常(错误日志中有记录通信…...
进程的创建
关于进程创建的一些细节都在注释中 /*#include <sys/types.h>#include <unistd.h>pid_t fork(void);函数的作用:用于创建子进程。返回值:fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。在父进程中返回创建的子…...

day-08 构造限制重复的字符串
思路 首先统计每个字符的个数,然后从后向前按照题意添加字符 解题方法 从后向前添加字符:1.当前字符个数<repeatLimit,直接添加 2.当前字符个数>repeatLimit,添加repeatLimit个,然后插入一个下一级字符 时间复杂度:O(n) 空间复杂度:…...

Java锁的分类
系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…...

SQL-分组查询
🎉欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克🍹 ✨博客主页:小小恶斯法克的博客 🎈该系列文章专栏:重拾MySQL 🍹文章作者技术和水平很有限,如果文中出现错误&am…...

HBase 基础
HBase 基础 HBase1. HBase简介1.1 HBase定义1.2 HBase数据模型1.2.1 HBase逻辑结构1.2.2 HBase物理存储结构1.2.3 数据模型 1.3 HBase基本架构 2. HBase环境安装2.1 HBase 安装部署2.1.1 HBase 本地按照2.1.2 HBase 伪分布模式安装2.1.3 HBase 集群安装 2.2 HBase Shell操作2.2…...
android 11添加系统api供app使用
实现要求:添加系统api到sdk,公开给未签名app使用 1.将代码添加到 frameworks/base/core/java 目录下,创建自己的包名,这个路径下可以自动识别,更新到current.txt,不用改编译规则 比如:framework…...

im6ull学习总结(三-3)freetype
1、Freetype简介 FreeType是一个开源的字体渲染引擎,主要用于将字体文件转换为位图或矢量图形,并在屏幕上渲染出高质量的字体。它提供了一组API,使开发者能够在自己的应用程序中使用和呈现字体。 FreeType最初是作为一个独立项目开发的&…...

基于OpenCV的谷物颗粒识别
基于OpenCV的谷物颗粒识别 一、程序整体功能介绍1.1 导入库与函数定义1.2 颜色分割与灰度处理1.3 二值化与轮廓检测1.4 绘制与计数1.5 主程序与结果展示 二、算法原理与实现流程2.1算法原理(1)颜色分割(2)灰度处理与二值化&#x…...

Aloha 机械臂的学习记录3——AWE:Pycharm运行代码记录
之前的博客创作了三偏关于Aloha_AWE的liunx终端指令运行代码的示例: Aloha 机械臂的学习记录——AWE:Bimanual Simulation Suite: https://blog.csdn.net/qq_54900679/article/details/134889183?spm1001.2014.3001.5502 Aloha 机械臂的学习记录1——AWE&#x…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...