深入源码设计!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博客 在本…...
vue echarts画多柱状图+多折线图
<!--多柱状图折线图--> <div class"echarts-box" id"multiBarPlusLine"></div>import * as echarts from echarts;mounted() {this.getMultiBarPlusLine() },getMultiBarPlusLine() {const container document.getElementById(multiBar…...
cesium for unity 打包webgl失败,提示不支持
platform webgl is not supported with HDRP use the Vulkan graphics AR instead....
python开发基础——day7 序列类型方法
一、初识序列类型方法 序列类型的概念:数据的集合,在序列类型里面可以存放任意的数据,也可以对数据进行更方便的操作,这个操作是叫增删改查(crud) ( 增加(Creat),读取查询(Retrieve),更新(Update)…...
用java写一个二叉树翻转
class TreeNode {int val;TreeNode left, right;TreeNode(int val) {this.val val;left right null;} }public class BinaryTree {TreeNode root;// 递归翻转二叉树public TreeNode invertTree(TreeNode root) {if (root null) {return null;}// 递归翻转左子树和右子树Tre…...
数学建模系列(3/4):典型建模方法
目录 引言 1. 回归分析 1.1 线性回归 基本概念 Matlab实现 1.2 多元回归 基本概念 Matlab实现 1.3 非线性回归 基本概念 Matlab实现 2. 时间序列分析 2.1 时间序列的基本概念 2.2 移动平均 基本概念 Matlab实现 2.3 指数平滑 基本概念 Matlab实现 2.4 ARIM…...
AI播客下载:Machine Learning Street Talk(AI机器学习)
该频道由 Tim Scarfe 博士、Yannic Kilcher 博士和 Keith Duggar 博士管理。 他们做了出色的工作,对每个节目进行了彻底的研究,并与机器学习行业中一些受过最高教育、最全面的嘉宾进行了双向对话。 每一集都会教授一些新内容,并且提供未经过滤…...
鱼缸补水器工作原理是什么
鱼缸补水器是一种应用广泛的智能设备,主要用于自动监测和补充鱼缸内的水位,以确保鱼类生存环境的稳定。其工作原理简单而高效,为饲主提供了方便和安全的使用体验。 该补水器通常由两部分组成:控制器和吸盘。首先,用户…...
Linux-Tomcat服务配置到系统服务
目录 前言一、系统环境二、配置步骤step1 了解环境的安装路径step2 配置生成tomcat.pid文件step3 配置tomcat.service文件 三、测试systemctl命令管理Tomcat服务3.1 systemctl命令启动Tomcat服务3.2 systemctl命令查看Tomcat服务3.3 systemctl命令关闭Tomcat服务3.4 systemctl命…...
Python抓取高考网图片
Python抓取高考网图片 一、项目介绍二、完整代码一、项目介绍 本次采集的目标是高考网(http://www.gaokao.com/gkpic/)的图片,实现图片自动下载。高考网主页如下图: 爬取的流程包括寻找数据接口,发送请求,解析图片链接,向图片链接发送请求获取数据,最后保存数据。 二…...
Vue配置项data
data 目录 data 目录类型介绍关键原理编译过程 Vue2Vue3 📌Vue.js 中的 data(Obj/Function)属性是 Vue 实例的一个配置选项 类型介绍 对象式 对于根实例或者非复用组件,通常直接提供一个对象字面量作为 data 的值。在对象式中…...
东莞建设培训中心网站/手机端百度收录入口
i.MX6ULL终结者三大手册,加速学习和开发速度,一秒化无形! 《嵌入式Linux开发指南》《开发板使用手册》《裸机使用手册》 详细手册点击链接下载:https://pan.baidu.com/s/1Xat4C-cDa2Gi1UwNckNRTw 提取码:064r 前言 总领及学习指…...
网站建设如何存数据/营销策划与运营团队
Ext.window.MessageBox.confirmconfirm( String title, String msg, [Function fn], [Object scope]function指定选择弹出框选择的是确定还是取消function(op){//如果点击的是确定if(op "yes"){}else{}}prompt( String title, String msg, [Function fn], [Object s…...
自助建站 源码/seo初学教程
关于network partition网络设备故障导致的网络分裂。比如,存在A\B\C\D\E五个节点,A\B处于同一子网,B\C\D处于另外一子网,中间通过交换机相连。若两个子网间的交换机故障了即发生了网络分区,A\B和C\D\E便不能通讯。某些…...
厦门房地产网站建设/小红书关键词热度查询
K8s已经成为一线大厂分布式平台的标配技术。你是不是还在惆怅怎么掌握它?来这里,大型互联网公司一线工程师亲授,不来虚的,直接上手实战,3天时间带你搭建K8s平台,快速学会K8s,点击下方图片可了解…...
广东品牌网站建设多少钱/看广告赚钱的平台
如果觉得我的算法分享对你有帮助,欢迎关注我的微信公众号“ 圆圆的算法笔记”,更多算法笔记和世间万物的学习记录~ 1. 背景 CTR预估是搜索、推荐、广告等领域基础且重要的任务,主要目标是预测用户在当前上下文环境下对某一个候选(视频、商品、广告等) 发生点击的概率。C…...
wordpress插件导出/seo网站内部优化方案
1、修改自己的shellchsh [shell路径] 2、修改其它人的shellchsh [shell路径] [用户登陆名]...