<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          LRU 緩存-keep-alive 實(shí)現(xiàn)原理

          共 7979字,需瀏覽 16分鐘

           ·

          2021-12-16 21:20


          本文首發(fā)于政采云前端團(tuán)隊(duì)博客:LRU 緩存-keep-alive 實(shí)現(xiàn)原理

          https://www.zoo.team/article/lru-keep-alive

          前言

          相信大部分同學(xué)在日常需求開發(fā)中或多或少的會(huì)有需要一個(gè)組件狀態(tài)被持久化、不被重新渲染的場(chǎng)景,熟悉 vue 的同學(xué)一定會(huì)想到 keep-alive 這個(gè)內(nèi)置組件。

          那么什么是 keep-alive 呢?

          keep-alive 是 Vue.js 的一個(gè) 內(nèi)置組件。它能夠?qū)⒉换顒?dòng)的組件實(shí)例保存在內(nèi)存中,而不是直接將其銷毀,它是一個(gè)抽象組件,不會(huì)被渲染到真實(shí) DOM 中,也不會(huì)出現(xiàn)在父組件鏈中。簡(jiǎn)單的說,keep-alive用于保存組件的渲染狀態(tài),避免組件反復(fù)創(chuàng)建和渲染,有效提升系統(tǒng)性能。keep-alive ?的 ?max 屬性,用于限制可以緩存多少組件實(shí)例,一旦這個(gè)數(shù)字達(dá)到了上限,在新實(shí)例被創(chuàng)建之前,已緩存組件中最久沒有被訪問的實(shí)例會(huì)被銷毀掉,而這里所運(yùn)用到的緩存機(jī)制就是 LRU 算法

          LRU 緩存淘汰算法

          LRU( least recently used)根據(jù)數(shù)據(jù)的歷史記錄來淘汰數(shù)據(jù),重點(diǎn)在于保護(hù)最近被訪問/使用過的數(shù)據(jù),淘汰現(xiàn)階段最久未被訪問的數(shù)據(jù)

          LRU的主體思想在于:如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高

          1. 新數(shù)據(jù)插入到鏈表尾部;
          2. 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表尾部
          3. 當(dāng)鏈表滿的時(shí)候,將鏈表頭部的數(shù)據(jù)丟棄。

          實(shí)現(xiàn)LRU的數(shù)據(jù)結(jié)構(gòu)

          經(jīng)典的 LRU 一般都使用 ?hashMap ?+ 雙向鏈表。考慮可能需要頻繁刪除一個(gè)元素,并將這個(gè)元素的前一個(gè)節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn),所以使用雙鏈接最合適。并且它是按照結(jié)點(diǎn)最近被使用的時(shí)間順序來存儲(chǔ)的。如果一個(gè)結(jié)點(diǎn)被訪問了, 我們有理由相信它在接下來的一段時(shí)間被訪問的概率要大于其它結(jié)點(diǎn)。

          不過既然已經(jīng)在 js 里都已經(jīng)使用?Map?了,為何不直接取用現(xiàn)成的迭代器獲取下一個(gè)結(jié)點(diǎn)的 key 值(keys().next()

          //?./LRU.ts
          export?class?LRUCache?{
          ??capacity:?number;?//?容量
          ??cache:?Map<number,?number?|?null>;?//?緩存
          ??constructor(capacity:?number)?{
          ????this.capacity?=?capacity;
          ????this.cache?=?new?Map();
          ??}
          ??get(key:?number):?number?{
          ????if?(this.cache.has(key))?{
          ??????let?temp?=?this.cache.get(key)?as?number;
          ??????//訪問到的?key?若在緩存中,將其提前
          ??????this.cache.delete(key);
          ??????this.cache.set(key,?temp);
          ??????return?temp;
          ????}
          ????return?-1;
          ??}
          ??put(key:?number,?value:?number):?void?{
          ????if?(this.cache.has(key))?{
          ??????this.cache.delete(key);
          ??????//存在則刪除,if?結(jié)束再提前
          ????}?else?if?(this.cache.size?>=?this.capacity)?{
          ??????//?超過緩存長(zhǎng)度,淘汰最近沒使用的
          ??????this.cache.delete(this.cache.keys().next().value);
          ??????console.log(`refresh:?key:${key}?,?value:${value}`)
          ????}
          ????this.cache.set(key,?value);
          ??}
          ??toString(){
          ????console.log('capacity',this.capacity)
          ????console.table(this.cache)
          ??}
          }
          //?./index.ts
          import?{LRUCache}?from?'./lru'
          const?list?=?new?LRUCache(4)
          list.put(2,2)???//?入?2,剩余容量3
          list.put(3,3)???//?入?3,剩余容量2
          list.put(4,4)???//?入?4,剩余容量1
          list.put(5,5)???//?入?5,已滿????從頭至尾?????????2-3-4-5
          list.put(4,4)???//?入4,已存在?——>?置隊(duì)尾?????????2-3-5-4
          list.put(1,1)???//?入1,不存在?——>?刪除隊(duì)首?插入1??3-5-4-1
          list.get(3)?????//?獲取3,刷新3——>?置隊(duì)尾?????????5-4-1-3
          list.toString()
          //?./index.ts
          import?{LRUCache}?from?'./lru'
          const?list?=?new?LRUCache(4)

          list.put(2,2)???//?入?2,剩余容量3
          list.put(3,3)???//?入?3,剩余容量2
          list.put(4,4)???//?入?4,剩余容量1
          list.put(5,5)???//?入?5,已滿????從頭至尾??????2-3-4-5
          list.put(4,4)???//?入4,已存在?——>?置隊(duì)尾????? 2-3-5-4
          list.put(1,1)???//?入1,不存在?——>?刪除隊(duì)首?插入1??3-5-4-1
          list.get(3)?????//?獲取3,刷新3——>?置隊(duì)尾??????5-4-1-3
          list.toString()

          結(jié)果如下:

          vue 中 Keep-Alive

          原理

          1. 使用 ?LRU 緩存機(jī)制進(jìn)行緩存,max 限制緩存表的最大容量
          2. 根據(jù)設(shè)定的 include/exclude(如果有)進(jìn)行條件匹配,決定是否緩存。不匹配,直接返回組件實(shí)例
          3. 根據(jù)組件 ID 和 tag 生成緩存 ?Key ,并在緩存對(duì)象中查找是否已緩存過該組件實(shí)例。如果存在,直接取出緩存值并更新該 key 在 this.keys 中的位置(更新 key 的位置是實(shí)現(xiàn) LRU 置換策略的關(guān)鍵)
          4. 獲取節(jié)點(diǎn)名稱,或者根據(jù)節(jié)點(diǎn) cid 等信息拼出當(dāng)前組件名稱
          5. 獲取 keep-alive 包裹著的第一個(gè)子組件對(duì)象及其組件名

          源碼分析

          初始化 keepAlive 組件
          const?KeepAliveImpl:?ComponentOptions?=?{
          ??name:?`KeepAlive`,
          ??props:?{
          ????include:?[String,?RegExp,?Array],
          ????exclude:?[String,?RegExp,?Array],
          ????max:?[String,?Number],
          ??},
          ??setup(props:?KeepAliveProps,?{?slots?}:?SetupContext)?{
          ????//?初始化數(shù)據(jù)
          ????const?cache:?Cache?=?new?Map();
          ????const?keys:?Keys?=?new?Set();
          ????let?current:?VNode?|?null?=?null;
          ????//?當(dāng)?props?上的?include?或者?exclude?變化時(shí)移除緩存
          ????watch(
          ??????()?=>?[props.include,?props.exclude],
          ??????([include,?exclude])?=>?{
          ??????include?&&?pruneCache((name)?=>?matches(include,?name));
          ??????exclude?&&?pruneCache((name)?=>?!matches(exclude,?name));
          ??????},
          ??????{?flush:?"post",?deep:?true?}
          ????);
          ????//?緩存組件的子樹?subTree
          ????let?pendingCacheKey:?CacheKey?|?null?=?null;
          ????const?cacheSubtree?=?()?=>?{
          ??????//?fix?#1621,?the?pendingCacheKey?could?be?0
          ??????if?(pendingCacheKey?!=?null)?{
          ????????cache.set(pendingCacheKey,?getInnerChild(instance.subTree));
          ??????}
          ????};
          ????// KeepAlive 組件的設(shè)計(jì),本質(zhì)上就是空間換時(shí)間。
          ????//?在?KeepAlive?組件內(nèi)部,
          ????//?當(dāng)組件渲染掛載和更新前都會(huì)緩存組件的渲染子樹?subTree
          ????onMounted(cacheSubtree);
          ????onUpdated(cacheSubtree);
          ????onBeforeUnmount(()?=>?{
          ????//?卸載緩存表里的所有組件和其中的子樹...
          ????}
          ????return?()=>{
          ??????//?返回?keepAlive?實(shí)例
          ????}
          ??}
          }

          return?()=>{
          ??//?省略部分代碼,以下是緩存邏輯
          ??pendingCacheKey?=?null
          ??const?children?=?slots.default()
          ??let?vnode?=?children[0]
          ??const?comp?=?vnode.type?as?Component
          ??const?name?=?getName(comp)
          ??const?{?include,?exclude,?max?}?=?props
          ??//?key?值是?KeepAlive?子節(jié)點(diǎn)創(chuàng)建時(shí)添加的,作為緩存節(jié)點(diǎn)的唯一標(biāo)識(shí)
          ??const?key?=?vnode.key?==?null???comp?:?vnode.key
          ??//?通過?key?值獲取緩存節(jié)點(diǎn)
          ??const?cachedVNode?=?cache.get(key)
          ??if?(cachedVNode)?{
          ????//?緩存存在,則使用緩存裝載數(shù)據(jù)
          ????vnode.el?=?cachedVNode.el
          ????vnode.component?=?cachedVNode.component
          ????if?(vnode.transition)?{
          ??????//?遞歸更新子樹上的?transition?hooks
          ??????setTransitionHooks(vnode,?vnode.transition!)
          ????}
          ??????//?阻止?vNode?節(jié)點(diǎn)作為新節(jié)點(diǎn)被掛載
          ??????vnode.shapeFlag?|=?ShapeFlags.COMPONENT_KEPT_ALIVE
          ??????//?刷新key的優(yōu)先級(jí)
          ??????keys.delete(key)
          ??????keys.add(key)
          ??}?else?{
          ??????keys.add(key)
          ??????//?屬性配置?max?值,刪除最久不用的?key?,這很符合?LRU?的思想
          ??????if?(max?&&?keys.size?>?parseInt(max?as?string,?10))?{
          ????????pruneCacheEntry(keys.values().next().value)
          ??????}
          ????}
          ????//?避免?vNode?被卸載
          ????vnode.shapeFlag?|=?ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
          ????current?=?vnode
          ????return?vnode;
          }
          將組件移出緩存表
          //?遍歷緩存表
          function?pruneCache(filter?:?(name:?string)?=>?boolean)?{
          ??cache.forEach((vnode,?key)?=>?{
          ????const?name?=?getComponentName(vnode.type?as?ConcreteComponent);
          ????if?(name?&&?(!filter?||?!filter(name)))?{
          ??????//?!filter(name)?即?name?在?includes?或不在?excludes?中
          ??????pruneCacheEntry(key);
          ????}
          ??});
          }
          //?依據(jù)?key?值從緩存表中移除對(duì)應(yīng)組件
          function?pruneCacheEntry(key:?CacheKey)?{
          ??const?cached?=?cache.get(key)?as?VNode;
          ??if?(!current?||?cached.type?!==?current.type)?{
          ????/*?當(dāng)前沒有處在?activated?狀態(tài)的組件
          ?????*?或者當(dāng)前處在?activated?組件不是要?jiǎng)h除的?key?時(shí)
          ?????*?卸載這個(gè)組件
          ????*/

          ????unmount(cached);?//?unmount方法里同樣包含了?resetShapeFlag
          ??}?else?if?(current)?{
          ????//?當(dāng)前組件在未來應(yīng)該不再被?keepAlive?緩存
          ????//?雖然仍在?keepAlive?的容量中但是需要刷新當(dāng)前組件的優(yōu)先級(jí)
          ????resetShapeFlag(current);
          ????//?resetShapeFlag?
          ??}
          ??cache.delete(key);
          ??keys.delete(key);
          }
          function?resetShapeFlag(vnode:?VNode)?{
          ??let?shapeFlag?=?vnode.shapeFlag;?//?shapeFlag?是?VNode?的標(biāo)識(shí)
          ???//?...?清除組件的?shapeFlag
          }

          keep-alive案例

          本部分將使用 vue 3.x 的新特性來模擬 ?keep-alive ?的具體應(yīng)用場(chǎng)景

          在 index.vue 里我們引入了 CountUp 、timer 和 ColorRandom 三個(gè)帶有狀態(tài)的組件 在容量為 2 的 中包裹了一個(gè)動(dòng)態(tài)組件

          //?index.vue
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    久久抽插视频 | 青青操网| 国产乱人伦久久免费 | 亚洲sv视频 | 夜色五月丁香久久 |