当前位置: 首页 > Web前端 > vue.js

keep-alive的实现原理和LRU缓存策略

时间:2023-03-31 22:31:38 vue.js

文章首发于个人博客keep-alive的用法我们先看看官方文档中的keep-alive的用法。props:include:只有名称匹配的组件才会被缓存exclude:任何名称匹配的组件都不会被缓存max:The可以缓存的最大组件实例数。(2.5.0新增,一旦达到这个数量,在创建新实例前,缓存组件中最长时间未被访问的实例将被销毁)将缓存Component实例而不是销毁它们。当组件切换到keep-alive时,会执行其activated和deactivated两个生命周期钩子函数。实现原理源码分析是vue源码中实现的一个组件,我们可以从源码来分析,基于vue2.6.11版本,源码位置src/core/components/keep-alive.js//组件的实现也是一个对象exportdefault{name:'keep-alive',//抽象组件abstract:true,props:{//只有名字匹配的组件才会被缓存include:patternTypes,//任何名字匹配的组件都不会被缓存exclude:patternTypes,//缓存组件的最大个数,因为我们缓存了vnode对象,它也会持有DOM,当我们缓存很多的时候,需要占用更多内存,所以这个配置允许我们指定缓存大小max:[String,Number]},created(){//初始化缓存对象,用于存储缓存和缓存vNode键数组this.cache=Object.create(null)this.keys=[]},//在destroyeddestroy中销毁缓存中的所有组件实例ed(){for(constkeyinthis.cache){pruneCacheEntry(this.cache,key,this.keys)}},mounted(){//监听include和exclude变化,变化时重新调整缓存内容//其实就是遍历缓存,当发现缓存的节点名与新规则不匹配时,将缓存的节点从缓存中移除。this.$watch('include',val=>{pruneCache(this,name=>matches(val,name))})这个。$watch('exclude',val=>{pruneCache(this,name=>!matches(val,name))})},//自定义渲染函数render(){/**获取第一个子元素的vnode*由于我们也在标签里面写了DOM,所以我们可以先获取它的默认插槽,然后获取它的第一个子节点只处理第一个子元素,所以它通常与组件动态组件或router-view一起使用,这一点要记住。*/constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)constcomponentOptions:?VNodeComponentOptions=vnode&&vnode.componentOptionsif(componentOptions){//检查模式//确定当前组件名称并包含,exclude之间的关系:constname:?string=getComponentName(componentOptions)const{include,exclude}=this//matches就是做匹配,分别处理数组,字符串,正则表达式//如果组件名满足配置include不匹配或者配置了exclude和matches,则直接返回该组件的vnode,否则进入下一步缓存:if(//notinclude(include&&(!name||!matches(include,name)))||//exclude(exclude&&name&&matches(exclude,name))){returnvnode}const{cache,keys}=thisconstkey:?string=vnode.key==null//相同的构造函数可能会注册为不同的本地组件//所以cidalone还不够(#3269)吗?componentOptions.Ctor.cid+(componentOptions.tag?`::${componentOptions.tag}`:''):vnode.key//如果命中缓存,直接从缓存中获取vnode的组件实例,重新调整key顺序放在最后anif(cache[key]){vnode.componentInstance=cache[key].componentInstance//使当前key最新鲜//使用LRU缓存策略移除key并在末尾添加remove(keys,key)keys.push(key)}else{//如果没有命中缓存,则将vnode设置到缓存中超过this.max,需要从缓存中删除第一个if(this.max&&keys.length>parseInt(this.max)){//除了从缓存中删除外,还需要判断是否要删除的缓存的组件标签不是当前渲染的组件标签,也执行删除缓存组件实例的$destroy方法pruneCacheEntry(cache,keys[0],keys,this._vnode)}}//keepAlive标志vnode.data.keepAlive=true}returnvnode||(slot&&slot[0])}}functionpruneCacheEntry(cache:VNodeCache,key:string,keys:Array,current?:VNode){constcached=cache[key]if(缓存&&(!current||cached.tag!==current.tag)){cached.componentInstance.$destroy()}cache[key]=nullremove(keys,key)}1.判断当前组件是否缓存获取保活包的第一个子组件对象和组件名,根据设置的include/exclude(如果有)进行条件匹配,决定是否缓存。如果不匹配,直接返回组件实例2,如果命中缓存,直接获取,同时更新key的位置,根据组件id和tag生成缓存key,校验组件实例对象是否已经缓存在缓存对象中。如果存在,则直接取出缓存的值,更新key在this.keys中的位置(更新key的位置是实现LRU替换策略的关键)3.不命中则设置到缓存中缓存,检查缓存实例数是否超过max,在this.cache对象中存储组件实例并保存key值,然后检查缓存实例数是否超过max的设置值。如果超过max的设置值,则按照LRU替换策略删除最近未使用的实例(即下标为0)4.将当前组件实例的keepAlive属性设置为true,缓存时会使用选择过程。abstract(抽象组件)最初将abstract属性值设置为true,即为抽象组件。文档中提到:是一个抽象组件:它不会自己渲染一个DOM元素,也不会出现在父组件链中。一旦组件被缓存,再次渲染时将不会执行created、mounted等钩子函数。但是有些业务场景需要在缓存组件重新渲染的时候做一些事情,vue提供了activated和deactivated钩子函数。Vue在初始化生命周期时,在为一个组件实例建立父子关系时,会根据抽象属性决定是否忽略一个组件。在keep-alive中,如果设置了abstract:true,Vue将跳过组件实例。exportfunctioninitLifecycle(vm:Component){constoptions=vm.$options//定位第一个非抽象父级letparent=options.parentif(parent&&!options.abstract){while(parent.$options.abstract&&parent.$parent){parent=parent.$parent}parent.$children.push(vm)}...}第一次渲染时缓存第一次渲染,除了在<中创建keep-alive>缓存,设置vnode.data.keepAlive为true,其他过程同普通组件。缓存渲染时,会根据vnode.componentInstance(第一次渲染时vnode.componentInstance未定义)和vnode.data.keepAlive进行判断。它不会执行组件的created、mounted等钩子函数,而是对缓存的组件执行patch过程,最后将缓存的DOM对象直接插入到目标元素中,数据更新情况下的渲染过程完成了。缓存策略LRU缓存策略:从内存中找出最长未使用的数据来替换新的数据。LRU(Leastrecentlyused)算法根据数据的历史访问记录来剔除数据。核心思想是“如果数据最近被访问过,那么以后被访问的概率也更高”。最常见的实现是使用链表来存储缓存数据。具体算法实现如下:新数据插入链表头部。每当缓存命中(即缓存数据被访问),数据就被移到链表的头部。当链表满时,链表尾部的数据被丢弃。总结是一个抽象组件。当第一次渲染设置缓存时,组件创建和挂载的钩子函数不会在缓存渲染时执行。相反,缓存的组件将被修补并最终直接更新到目标元素。使用LRU缓存策略缓存组件命中缓存,然后直接返回缓存,同时如果没有命中缓存更新缓存key的位置,设置到缓存中,检查是否缓存实例数超过最大值。参考vuekeep-alive的实现原理和缓存策略Vue中keep-alive实现原理的简单分析和LRU缓存算法缓存淘汰算法--LRU算法leetcode146。LRU缓存机制Others最近推出了前端100天进阶计划,主要是深入挖掘每个知识点背后的原理,欢迎关注微信公众号“牧马星”,一起学习,签到100天。