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…...

Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...