深入源码设计!Vue3.js核心API——Computed实现原理
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~
作者:前端小王hs
阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主
此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来
书籍:《Vue.js设计与实现》 作者:霍春阳
本篇博文将在书第4.8节的基础上进一步解析,附加了测试的代码运行示例,以及对书籍中提到的ES6中的数据结构及其特点进行阐述,方便正在学习Vue3或想分析Vue3源码的朋友快速阅读
如有帮助,不胜荣幸
前置章节:
- 深入理解Vue3.js响应式系统基础逻辑
- 深入理解Vue3.js响应式系统设计之栈结构和循环问题
- 深入理解Vue3.js响应式系统设计之调度执行
懒执行lazy的effect
经过前置的章节的学习,可以发现设计的effect
都是立即执行的,而在4.8节的开始,作者向我们设计了一个可以按意愿时间节点执行的effect
,其设计逻辑非常简单,就是在传递effect
的options
形参时添加内置lazy
属性,然后再在effect
函数内部进行判断,如果存在lazy
,则返回effectFn
给调用者。那么何时何地执行effectFn
的决定权就到了调用者的手上
传递lazy
代码如下:
effect(// 指定了 lazy 选项,这个函数不会立即执行() => {console.log(obj.foo)},// options{lazy: true}
)
完善判断lazy
的effect
代码如下:
function effect(fn, options = {}) {const effectFn = () => {cleanup(effectFn)activeEffect = effectFneffectStack.push(effectFn)fn()effectStack.pop()activeEffect = effectStack[effectStack.length - 1]}effectFn.options = optionseffectFn.deps = []// 只有非 lazy 的时候,才执行if (!options.lazy) {// 执行副作用函数effectFn()}// 将副作用函数作为返回值返回return effectFn
}
那么现在,就可以使用一个变量去接收返回的effectFn
,代码如下:
const effectFn = effect(() => { console.log(obj.foo)
}, { lazy: true }) // 手动执行副作用函数
effectFn()
对于手动执行,我们可以使用res
去接收调用函数
的返回值,那么就需要在effect
内return res
,代码又得继续完善:
function effect(fn, options = {}) {const effectFn = () => {cleanup(effectFn)activeEffect = effectFneffectStack.push(effectFn)// 将 fn 的执行结果存储到 res 中const res = fn() // 新增effectStack.pop()activeEffect = effectStack[effectStack.length - 1]// 将 res 作为 effectFn 的返回值return res // 新增}effectFn.options = optionseffectFn.deps = []if (!options.lazy) {effectFn()}return effectFn
}
那么重点来了,lazy
和本节要实现的computed
的关系在哪?(建议看完整篇再回过来看这个问题)
原因在于computed
内部使用lazy
去封装了getter
,什么是getter
?我们接着继续看
getter
从实现层面上看,getter
是computed
接收的实参的昵称,代码如下:
function computed(getter) {...}
MDN的定义是:get
语法将对象属性绑定到查询该属性时将被调用的函数,有点抽象是不是?我们看文档中例子就行,代码如下:
const obj = {log: ['a', 'b', 'c'],get latest() {return this.log[this.log.length - 1];},
};console.log(obj.latest);
// Expected output: "c"
可以看到,在obj
中使用了get functonName(){}
的写法,那么这个作用是什么呢?答案是可以利用obj.functionName
去返回动态计算值的属性,特别是不想以显示的方式返回的时候
当然,我们会可能会想,这种方式好像跟直接写函数差不太多,即下列代码所示:
const obj = {log: ['a', 'b', 'c'],latest() {return this.log[this.log.length - 1];},
};console.log(obj.latest());
// Expected output: "c"
区别在于使用get
在调用时无需加()
,使其看起来像调用一个属性,这样更简洁和更具有语义性,且要执行的逻辑只是计算内部的属性值,这就是使用get
去计算的意义
实现computed
computed
是Vue响应式的核心API
,用于声明计算属性,计算属性是依赖于其他的属性依赖而存在的,如果其他的属性依赖变化了,那么就会触发computed
进行重新计算,进而得到最新的计算属性,我们可以看下书中的示例,代码如下:
const sumRes = computed(() => obj.foo + obj.bar);effect(() => {// 在该副作用函数中读取 sumRes.valueconsole.log(sumRes.value);
});// 修改 obj.foo 的值
obj.foo++;
在这段示例代码中,sumRes
就是声明的计算属性,其依赖于obj.foo
和obj.bar
两个属性依赖,当obj.foo++
执行后,会触发computed
进而得到新的sumRes
需要注意的是,computed
还具有缓存,即如果属于依赖不变的情况下,无论执行多少次,都不会触发computed
的重新计算
那么computed
是如何设计的呢?
其实读到这里,聪明的读者应该有个思路,就是sumRes
就是getter
所对应的obj
,而sumRes.value
就是触发了obj.getter
,只不过这个getter
是get value(){}
,也就是:
const sumRes = {get value(){return ...}
}
初次实现computed
我们直接来看书中的源码是如何实现满足缓存的,代码如下:
function computed(getter) {// value 用来缓存上一次计算的值let value;// dirty 标志,用来标识是否需要重新计算值,为 true 则意味着“脏”,需要计算let dirty = true;const effectFn = effect(getter, {lazy: true});const obj = {get value() {// 只有“脏”时才计算值,并将得到的值缓存到 value 中if (dirty) {value = effectFn();// 将 dirty 设置为 false,下一次访问直接使用缓存到 value 中的值dirty = false;}return value;}};return obj;
}
可以看到是添加了一个value
变量存储getter
计算过后的值,并设置了一个开关dirty
,第一次为true
即产生计算,而计算过后就会置为false
,下一次读取时就不会重新计算
但现在又出现了一个问题,如果下次读取时obj.foo
或obj.bar
发生了变化呢?在哪里将dirty
置为true
?
别忘了我们还可以在effect
中传入schedular
,也就是在调度器中置为true
,代码如下:
const effectFn = effect(getter, { lazy: true, // 添加调度器,在调度器中将 dirty 重置为 true scheduler() { dirty = true; }
});
执行的逻辑(简化)是怎么样的?
- 读取
sumRes.value
,触发getter
getter
执行时触发读取obj.foo
和obj.bar
- 此时
activeEffect
栈顶的是封装了gettet
的effectFn
obj.foo
和obj.bar
与effectFn
关联,执行完返回sumRes.value
- 当触发
obj.foo
改变时,取出封装了gettet
的effectFn
的执行 - 在
trigger
中取出schedular
执行,将ditry
置为true
但现在,并不会重新输出sumRes.value
的值
原因在于obj.foo
和obj.bar
关联的effect
是封装了gettet
的effectFn
,也就是:
effect(() => obj.foo + obj.bar, { lazy: true, // 添加调度器,在调度器中将 dirty 重置为 true scheduler() { dirty = true; }
});
只会重新执行() => obj.foo + obj.bar
,而不是console.log(sumRes.value)
,所以下一步是要解决如何修改完obj.foo
后能够重新执行console.log(sumRes.value)
的问题
完善computed
其实设计的关键在于schedular
,我们知道在trigger
中,如果存在schedular
会执行schedular
而不是effectFn
,所以可以在schedular
中执行console.log(sumRes.value)
,但真正执行的其实是执行封装其的effectFn
那该如何拿到这个console.log(sumRes.value)
呢?可以设计一个关联,就是obj.value
与封装console.log(sumRes.value)
这个effectFn
的关联
我们知道,在执行完获取sumRes.value
之后,此时此时activeEffect
栈顶的是封装了console.log(sumRes.value)
的effectFn
,那么就可以在执行完获取sumRes.value
之后调用track
,代码如下:
const obj = { get value() {if (dirty) {value = effectFn()dirty = false}// 当读取 value 时,手动调用 track 函数进行追踪track(obj, 'value')return value}
}
然后再在scheduler
中,调用trigger
,取出effectFn
执行,代码如下:
// computed内
const effectFn = effect(getter, {lazy: true,scheduler() {dirty = true;trigger(obj, 'value')},
});
那么现在,当obj.foo++
时,就会重新执行console.log(sumRes.value)
,也就实现了当obj.foo
或obj.bar
变化时,会重新执行console.log(sumRes.value)
这个effect
的效果,具体的逻辑可看下图:
这就是整个computed
的实现原理
小记
这是写的第四篇关于vue3.js
响应式设计的内容了,但发现看的人还是比较少的,不管是评论还是收藏数都几乎为
0,不知道是写的不好还是其他的原因
写的初衷还是如同开头说的那般,书中即使讲的明白,但如果缺少一定的基础,在逻辑上还是比较难梳理的,写出来一方面是方便我自己复习,另一方面也是希望能够帮助到想了解或者进阶学习Vue的同学
关于这篇,如果认真阅读了,最起码可以达到以下效果
- 学会如何去实现懒执行函数
- 了解和学习什么是
getter
- 知道Vue团队是如何设计和实现
computed
的 - 响应式系统在
computed
的实现逻辑 - 面试时问到上述也会答得出
- 其他…
谢谢大家的阅读,如有错误的地方请私信笔者
笔者会在近期整理后续章节的笔记发布至博客中,希望大家能多多关注前端小王hs!
相关文章:

深入源码设计!Vue3.js核心API——Computed实现原理
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~ 作者:前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…...

驾考小技巧:老北京布鞋!距离高考出分还剩3天,我却看到有些孩子已经拿了“满分”——早读(逆天打工人爬取热门微信文章解读)
我20年驾校4000多块钱,你呢? 引言Python 代码第一篇 洞见 距离高考出分还剩3天,我却看到有些孩子已经拿了“满分”第二篇 视频新闻结尾 引言 昨天的文章顺利发出 看来“梅西” 这两个字在我们这边 不是敏感词 只是很多个罗粉搞得有点过头了 …...
java-正则表达式 2
7. 复杂的正则表达式示例(续) 7.1 验证日期格式 以下正则表达式用于验证日期格式,例如YYYY-MM-DD。 import java.util.regex.*;public class RegexExample {public static void main(String[] args) {String[] dates {"2023-01-01&q…...
hadoop常见简单基础面试题
文章目录 hadoop简单基础面试题1. 请说下 HDFS 读写流程2. HDFS 在读取文件的时候,如果其中一个块突然损坏了怎么办3. HDFS 在上传文件的时候,如果其中一个 DataNode 突然挂掉了怎么办4. NameNode 在启动的时候会做哪些操作5.Secondary NameNode 了解吗&…...
泄漏检测(LDAR)在建档和检测过程中造假套路和不规范行为
第一章 建档环节造假和不规范 一、 企业行为: 企业为了节约检测费,采取部分建档,部分密封点检测的行为 二、 第三方检测公司不规范行为: 1、台账信息不准确,密封点命名不准确 &…...

Android CTS环境搭建
CTS即Compatibility Test Suite意为兼容性测试,是Google推出的Android平台兼容性测试机制。其目的是尽早发现不兼容性,并确保软件在整个开发过程中保持兼容性。只有通过CTS认证的设备才能合法的安装并使用Google market等Google应用。 搭建CTS测试环境需…...
比较Zig、Rust和C++
比较Zig、Rust和C这三种编程语言,我们可以从以下几个关键维度来进行: 设计理念 表格 语言 设计理念 Zig 简洁性、模块化、避免常见错误 Rust 内存安全、并发性、性能 C 性能优化、资源控制、可扩展性 内存安全 Zig通过严格的编译时检查、可选…...

路由的params参数,命名路由,路由的params参数,命名路由
上篇我们讲了vue路由的使用 今天我们来讲vue中路由的嵌套,路由的params参数,命名路由 一.路由的params参数 1.配置路由规则,使用children配置项: router:[{path:/about,component:About,},{path:component:Home,//通过children配置子路由c…...
java:CompletableFuture的简单例子
java:CompletableFuture的简单例子 package com.chz.myTest;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.uti…...
element的table获取当前表格行
需求:验证表格同一行的最低限价不能超过销售定价 思路:先获取当前行table的index,然后在做大小比较 1.局部html <el-table-column label"销售定价(元)" min-width"200px"><template slot"header"&…...

html做一个分组散点图图的软件
在HTML中创建一个分组散点图,可以结合JavaScript库如D3.js或Plotly.js来实现。这些库提供了强大的数据可视化功能,易于集成和使用。下面是一个使用Plotly.js创建分组散点图的示例: 要添加文件上传功能,可以让用户上传包含数据的文…...
【SQL】UNION 与 UNION ALL 的区别
在 SQL 中,UNION 和 UNION ALL 都用于将两个或多个结果集合并为一个结果集,但它们在处理重复数据方面有显著区别。以下是它们的详细区别: 1. UNION UNION 操作符用于合并两个或多个 SELECT 语句的结果集,并自动去除结果集中重复…...

分类判决界面---W-H、H-K算法
本篇文章是博主在人工智能等领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在AI学习笔记&#…...

Python基础教程(三十):math模块
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝Ὁ…...

你只是重新发现了一些东西
指北君关于另外一条思维路径的发现。 "自以为是"的顿悟时刻 有很多时候,我会"自以为是"的发现/发明一些东西。这种"自以为是"的时刻通常还带有一些骄傲自豪的情绪。这种感觉特别像古希腊博学家阿基米德 在苦思冥想如何测量不规则物体…...

【英伟达GPU的挑战者】Groq—AI大模型推理的革命者
目录 引言第一部分:Groq简介第二部分:Groq的特点与优势1、高性能推理加速2、近存计算技术3、专用ASIC芯片设计4、低延迟与高吞吐量5、成本效益分析6、易用性与集成性7、软件与硬件的协同设计 第三部分:Groq的使用指南1、准备工作2、简单使用样…...

Python学习路线
Python学习路线 领取资料 一、Python基础知识 Python入门:了解Python的安装方法、如何运行Python程序以及交互模式的使用,同时学习注释的添加方法。 数据类型:掌握Python中的各种数据类型,包括数字、布尔值、字符串、列表、元…...
C++ std::forward()
在线调试网站: https://wandbox.org/ #include <iostream> #include <thread> #include <mutex> void func(int &&args) {std::cout << args << std::endl; }int main () {int a 10;func(20); …...

常见的8种排序(含代码):插入排序、冒泡排序、希尔排序、快速排序、简单选择排序、归并排序、堆排序、基数排序
时间复杂度O(n^2) 1、插入排序 (Insertion Sort) 从第一个元素开始,该元素可以认为已经被排序;取出下一个元素,在已经排序的元素序列中从后向前扫描;如果该元素(已排序)大于新元素,将该元素移到…...

go语言day2
使用cmd 中的 go install ; go build 命令出现 go cannot find main module 错误怎么解决? go学习-问题记录(开发环境)go: cannot find main module; see ‘go help modules‘_go: no flags specified (see go help mod edit)-CSDN博客 在本…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...