vue2源码分析-keep-alive组件

news2024/11/23 5:04:51

简介

keep-aliveVue.js的一个内置组件。它能够将指定的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

具体用法咱们这里就不再细说了,今天主要是探讨它的源码。

本文vue版本为2.6.14

源码概览

我们先来看看 keep-alive 组件整体的源码。

export default {name: 'keep-alive',abstract: true, // 定义为抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。props: {include: patternTypes, // 需要缓存组件,支持字符串、正则表达式或一个数组exclude: patternTypes, // 不需要缓存组件,支持字符串、正则表达式或一个数组max: [String, Number] // 最大缓存个数},methods: {// 添加vnode和key到cache对象和keys数组,也就是缓存组件。cacheVNode() {const { cache, keys, vnodeToCache, keyToCache } = thisif (vnodeToCache) {const { tag, componentInstance, componentOptions } = vnodeToCachecache[keyToCache] = {name: getComponentName(componentOptions),tag,componentInstance,}keys.push(keyToCache)// LRU算法,如果触达max,则将最近最少使用的数组顶元素删除。if (this.max && keys.length > parseInt(this.max)) {// 简单理解,从缓存对象中删除某个组件pruneCacheEntry(cache, keys[0], keys, this._vnode)}this.vnodeToCache = null}}},/* created钩子中初始化cache对象和keys数组 */created () {this.cache = Object.create(null) // 缓存对象this.keys = [] // 缓存key数组},/* destroyed钩子中销毁所有cache中的组件实例 */destroyed () {for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)}},// 挂载时mounted () {this.cacheVNode()// 对include和exclude进行监听// 发现缓存的节点名称和新的规则没有匹配上的时候,就把这个缓存节点从缓存中摘除。this.$watch('include', val => {pruneCache(this, name => matches(val, name))})this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})},updated () {this.cacheVNode()},render () {// 获取默认插槽const slot = this.$slots.default// 获取第一个子元素的 vnodeconst vnode: VNode = getFirstComponentChild(slot)// 获取组件options,以便获取到nameconst componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {// 首先获取组件的name,没有name则获取组件标签名const name: ?string = getComponentName(componentOptions)const { include, exclude } = this// 如果没匹配上,则什么都不处理,直接返会组件的vnode,否则的话走缓存逻辑if (// not included(include && (!name || !matches(include, name))) ||// excluded(exclude && name && matches(exclude, name))) {return vnode}const { cache, keys } = this// 获取组件的key,没有key则通过组件cid和tag组合生成const key: ?string = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key// 如果之前缓存过,则直接从缓存中获取if (cache[key]) {vnode.componentInstance = cache[key].componentInstance// 确保是最新的。(LRU算法,最近最少使用)// 首先将该位置的key移除,然后将该key添加到数组末尾。// 这样的好处就是,当触达到max的时候将数组前面的踢出就可以了。remove(keys, key)keys.push(key)} else {// 否则进行缓存,不过是延迟到update再添加到缓存中this.vnodeToCache = vnodethis.keyToCache = key}// 添加keepAlive标记vnode.data.keepAlive = true}return vnode || (slot && slot[0])}
} 

下面我们来逐步分析。

created钩子

created钩子会创建一个cache对象,用来作为缓存容器,保存vnode节点。创建一个keys数组,用来保存缓存的key

/* created钩子中初始化cache对象和keys数组 */
created () {this.cache = Object.create(null) // 缓存对象this.keys = [] // 缓存key数组
}, 

destroyed钩子

destroyed钩子则在组件被销毁的时候遍历cache对象,来清除cache缓存中的所有组件实例。并且keys数组也会跟着清空。

/* destroyed钩子中销毁所有cache中的组件实例 */
destroyed () {for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)}
}, 

我们再来看看pruneCacheEntry方法。简单理解就是将cache对象中某key对应的vnode移除,keys数组中某key删除。

function pruneCacheEntry (cache: CacheEntryMap,key: string,keys: Array<string>,current?: VNode
) {// 通过key获取缓存组件const entry: ?CacheEntry = cache[key]// 组件销毁if (entry && (!current || entry.tag !== current.tag)) {entry.componentInstance.$destroy()}// cache对象该key置空cache[key] = null// keys数组,删除该keyremove(keys, key)
} 

接下来我们再来看看render函数。

render

render () {// 获取默认插槽const slot = this.$slots.default// 获取第一个子元素的 vnodeconst vnode: VNode = getFirstComponentChild(slot)// 获取组件options,以便获取到nameconst componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {// 首先获取组件的name,没有name则获取组件标签名const name: ?string = getComponentName(componentOptions)const { include, exclude } = this// 如果没匹配上,则什么都不处理,直接返会组件的vnode,否则的话走缓存逻辑if (// not included(include && (!name || !matches(include, name))) ||// excluded(exclude && name && matches(exclude, name))) {return vnode}const { cache, keys } = this// 获取组件的key,没有key则通过组件cid和tag组合生成const key: ?string = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key// 如果之前缓存过,则直接从缓存中获取if (cache[key]) {vnode.componentInstance = cache[key].componentInstance// 确保是最新的。(LRU算法,最近最少使用)// 首先将该位置的key移除,然后将该key添加到数组末尾。// 这样的好处就是,当触达到max的时候将数组前面的踢出就可以了。remove(keys, key)keys.push(key)} else {// 否则进行缓存,不过是延迟到update再添加到缓存中this.vnodeToCache = vnodethis.keyToCache = key}// 添加keepAlive标记vnode.data.keepAlive = true}return vnode || (slot && slot[0])
} 

render函数的逻辑也很清晰。

首先通过getFirstComponentChild获取第一个子组件,获取该组件的name(存在组件名则直接使用组件名,否则会使用tag)。

接下来会将这个name通过includeexclude属性进行匹配,匹配不成功(说明不需要进行缓存)则不进行任何操作直接返回vnode,否则走缓存逻辑。

要查找缓存,首先需要获取keykey的话优先获取组件的key,组件没有key则通过组件cidtag组合生成一个key

接下来的事情很简单,根据keythis.cache中查找,如果存在则说明之前已经缓存过了,直接将缓存的vnodecomponentInstance(组件实例)覆盖到目前的vnode上面。否则将vnode存储在cache中(不是实时的,而是在update方法中修改)。

然后添加keepAlive标记,这个标记有什么用呢?我们知道被keep-alive包裹的组件后面的渲染是不会触发createdmounted生命周期函数,而是会触发acitvateddeactivated生命周期函数。这个标记就是用来做触发生命周期函数的判断的。

最后返回vnode(有缓存时该vnodecomponentInstance已经被替换成缓存中的了)。

mounted钩子

mounted里面做了两件事情。

首先初始化的时候,如果组件是需要缓存的话则会进行缓存。

然后对includeexclude进行监听,当这两个值发生变化的时候,则会重新计算cachekeys中缓存的数据。

mounted () {// 如果有需要缓存的组件,则进行缓存this.cacheVNode()// 对include和exclude进行监听// 发现缓存的节点名称和新的规则没有匹配上的时候,就把这个缓存节点从缓存中摘除。this.$watch('include', val => {pruneCache(this, name => matches(val, name))})this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})
}, 

pruneCache方法主要是过滤目前缓存的vnodecache对象和缓存keykeys数组。并将不符合新规则的vnodekey进行移除。

function pruneCache (keepAliveInstance: any, filter: Function) {const { cache, keys, _vnode } = keepAliveInstance// 遍历cache对象for (const key in cache) {// 获取缓存组件const entry: ?CacheEntry = cache[key]if (entry) {const name: ?string = entry.name// 如果之前缓存的组件并不符合新的规则,则进行移除if (name && !filter(name)) {pruneCacheEntry(cache, key, keys, _vnode)}}}
} 

我们再来看看update钩子

updated钩子

update方法也是用来将组件添加进缓存。

因为之前render方法中,对需要缓存的组件而没有缓存的话会修改this.vnodeToCachethis.keyToCache的值(并不是直接缓存),进而会触发update方法。也就是延迟缓存。

updated () {this.cacheVNode()
}, 

总结

keep-alive组件的缓存是基于VNode节点的而不是直接存储DOM结构。它将满足条件的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。并且使用到了LRU算法(最近最少使用),每次触发缓存,就会将该缓存组件放到数组末尾,当触达到max后,会将最近最少使用的缓存组件进行剔除。

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaEE day2 初识web与HTML

初步了解相关知识 关于端口&#xff08;port&#xff09;&#xff1a;一个端口同一时间只能被一个进程监听&#xff0c;但是一个进程可以监听多个端口 URL的标准格式&#xff1a;协议名称&#xff1a;//主机/资源路径&#xff1f;查询字符串#文档片段 一般协议最常见的为htt…

Java基础之《netty(25)—handler链调用机制》

一、netty的handler的调用机制 1、使用自定义的编码器和解码器来说明netty的handler调用机制。 客户端发送long -> 服务器 服务端发送long -> 客户端 2、案例 二、客户端发送给服务端 1、服务端 NettyServer.java package netty.inboundhandlerAndOutboundhandler;i…

【C++】从0到1入门C++编程学习笔记 - 基础入门篇:程序流程结构

文章目录一、选择结构1.1 if 语句1.2 三目运算符1.3 switch语句二、循环结构2.1 while 循环语句2.2 do...while 循环语句2.3 for 循环语句2.4 嵌套循环三、跳转语句3.1 break 语句3.2 continue 语句3.3 goto 语句C/C支持最基本的三种程序运行结构&#xff1a;顺序结构、选择结构…

MySQL进阶——优化

1、选择最合适的字段属性 Mysql是一种关系型数据库&#xff0c;可以很好地支持大数据量的存储&#xff0c;但是一般来说&#xff0c;数据库中的表越小&#xff0c;在它上面执行的查询也就越快。因此&#xff0c;在创建表的时候&#xff0c;为了获得更好的性能&#xff0c;我们…

腾讯云HiFlow场景连接器 联动对象存储企业网盘,打通数据分发“最后一公里”

对云厂商和企业用户来说&#xff0c;随着数据规模的快速增长&#xff0c;企业除了对存储功能和性能的要求不断增加&#xff0c;也越来越注重数据分发的效率。在传统数据分发的过程中&#xff0c;数据管理员往往需要先在存储桶下载对应的客户方案/交付资料&#xff0c;再使用微信…

LINUX软中断-softirq

前言 关于linux的软中断的文章&#xff0c;在网上可以找到很多&#xff0c;但总觉着讲的都不够深入&#xff0c;打算自己写一下 软中断的感性认识 中断一旦被触发&#xff0c;本地cpu正在运行的不管是什么程序都要让路&#xff0c;让中断程序执行并且执行过程中不能被打断。…

分布式事务问题

4.2 分布式事务问题 4.2.1 什么是分布式事务 一次课程发布操作需要向数据库、redis、elasticsearch、MinIO写四份数据&#xff0c;这里存在分布式事务问题。 什么是分布式事务&#xff1f; 首先理解什么是本地事务&#xff1f; 平常我们在程序中通过spring去控制事务是利用…

Linux---进程优先级

目录 基本概念 查看系统进程 PRI and NI 用top命令更改已存在进程的nice&#xff1a; 其他概念 基本概念 cpu资源分配的先后顺序&#xff0c;就是指进程的优先权&#xff08;priority&#xff09;。 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很…

JNPF 3.4.5 快速开发框架源码目录截图 Gitee代码托管和研发协作平台

Gitee Gitee 除了提供最基础的 Git 代码托管之外&#xff0c;还提供代码在线查看、历史版本查看、Fork、Pull Request、打包下载任意版本、Issue、Wiki 、保护分支、代码质量检测、PaaS项目演示等方便管理、开发、协作、共享的功能。 作为一个应用项目&#xff0c;一般会有一…

flink环境参数引起的错误

环境参数&#xff1a;flink使用的版本是1.13.5、CentOS Linux 8一&#xff0c;默认环境引起本地与集群的jar包冲突遇到的情况是在idea执行的时候是没有问题的&#xff0c;然后打成jar包用集群执行的时候就会遇到问题。报错的时候会不太一&#xff0c;总之顺着错误去找的话会找到…

【力学性能预测】基于人工神经网络的钢板力学性能预测(附完整代码和数据集,系列3)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 Hello,大家好,我是augustqi。今天手把手带大家做一个机器学习实战项目:基于人工神经网络的钢板力学性能预测,或者称…

文本生成视频、AI临床知识理解、大模型有趣案例、智源社区《预训练周刊》第70期...

No.70智源社区预训练组预训练研究观点资源活动周刊订阅《预训练周刊》已经开启“订阅功能”&#xff0c;扫描下面二维码&#xff0c;进入《预训练周刊》主页&#xff0c;选择“关注TA”&#xff0c;即可收到推送消息。关于周刊本期周刊&#xff0c;我们选择了12篇来自国内外知名…

《机器人SLAM导航核心技术与实战》第1季:第4章_机器人传感器

视频讲解 【第1季】4.第4章_机器人传感器-视频讲解 【第1季】4.1.第4章_机器人传感器_惯性测量单元-视频讲解 【第1季】4.2.第4章_机器人传感器_激光雷达-视频讲解 【第1季】4.3.第4章_机器人传感器_相机-视频讲解 【第1季】4.4.第4章_机器人传感器_带编码器的减速电机-视频…

Python机器学习:数据探索与可视化(一)

什么是数据探索&#xff1f; 在前面我们说到&#xff0c;所谓机器学习&#xff0c;就是用已知的数据通过算法去预测未来未知的数据。但是这个过程进行的前提就是要保证已知数据的完成性。所以数据探索&#xff0c;就是检查数据是否完整&#xff0c;是否有缺失值。 什么是可视化…

【安全研究】基于OPA和Spring Security的外部访问控制

译者导读 CNCF的毕业项目Open Policy Agent&#xff08;OPA&#xff09;, 为策略决策需求提供了一个统一的框架与服务。它将策略决策从软件业务逻辑中解耦剥离&#xff0c;将策略定义、决策过程抽象为通用模型&#xff0c;实现为一个通用策略引擎&#xff0c;可适用于广泛的业…

阿里云对话 Tapdata:「开发者优先」正在影响商业化软件的开源选择

在刚刚过去的2022年&#xff0c;Tapdata 带着开源项目 PDK&#xff08;Plugin Development Kit&#xff09;及 Tapdata Community 和大家见面&#xff0c;兑现了我们对自己以及开发者们的开源承诺&#xff0c;同时与阿里云等生态伙伴联合&#xff0c;加速构建更加开放的数据生态…

Linux基础 - DNS服务进阶

‍‍&#x1f3e1;博客主页&#xff1a; Passerby_Wang的博客_CSDN博客-系统运维,云计算,Linux基础领域博主&#x1f310;所属专栏&#xff1a;『Linux基础』&#x1f30c;上期文章&#xff1a; Linux基础 - DNS服务基础&#x1f4f0;如觉得博主文章写的不错或对你有所帮助的话…

贪心策略(三)多机调度问题、活动选择(库函数sort的整理)

把sort库函数的使用总结一下&#xff1a; 1、头文件#include<algorithm> 时间复杂度nlog(n) 2、使用格式 sort&#xff08;arr.begin(), arr.end()&#xff09;&#xff1b; 3、默认使用升序排序&#xff0c;第三个参数默认使用less<T>() 4、如果需要进行降序排序…

springcloud + nacos多环境联调、本地联调(即灰度版本)

背景&#xff1a;当我们使用nacos为注册中心注册微服务时&#xff0c;想本地环境和测试环境公用一个nacos&#xff0c;即注册中心等基础服务共用。当我们在服务A开发时&#xff0c;本地服务和测试环境服务都是注册到同一个nacos&#xff0c;由于nacos自带负载均衡策略&#xff…

小程序开发经验分享(9)小程序快速上线汇总

微信小程序申请 开发中的Appid 需要从“微信公众平台”中获取 如果是直接从git上拉取的话 直接项目导入就可以了(名称可以是中文) 小程序基础配置 如果需要修改显示的名称和appid可以去生成的配置文件project.config.json里面修改