對keep-alive的理解,它是如何實現的,具體緩存的是什么?
- (1)keep-alive有以下三個屬性:
- 注意:keep-alive 包裹動態組件時,會緩存不活動的組件實例。
- 主要流程
- (2)keep-alive 的實現
- render函數
- (3)keep-alive 本身的創建過程和 patch 過程
- (4)LRU (least recently used)緩存策略
如果需要在組件切換的時候,保存一些組件的狀態防止多次渲染,就可以使用 keep-alive 組件包裹需要保存的組件。
(1)keep-alive有以下三個屬性:
include 字符串或正則表達式,只有名稱匹配的組件會被匹配;
exclude 字符串或正則表達式,任何名稱匹配的組件都不會被緩存;
max 數字,最多可以緩存多少組件實例。
注意:keep-alive 包裹動態組件時,會緩存不活動的組件實例。
主要流程
判斷組件 name ,不在 include 或者在 exclude 中,直接返回 vnode,說明該組件不被緩存。
獲取組件實例 key ,如果有獲取實例的 key,否則重新生成。
key生成規則,cid +“∶∶”+ tag ,僅靠cid是不夠的,因為相同的構造函數可以注冊為不同的本地組件。
如果緩存對象內存在,則直接從緩存對象中獲取組件實例給 vnode ,不存在則添加到緩存對象中。 5.最大緩存數量,當緩存組件數量超過 max 值時,清除 keys 數組內第一個組件。
(2)keep-alive 的實現
const patternTypes: Array<Function> = [String, RegExp, Array] // 接收:字符串,正則,數組export default {name: 'keep-alive',abstract: true, // 抽象組件,是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在父組件鏈中。props: {include: patternTypes, // 匹配的組件,緩存exclude: patternTypes, // 不去匹配的組件,不緩存max: [String, Number], // 緩存組件的最大實例數量, 由于緩存的是組件實例(vnode),數量過多的時候,會占用過多的內存,可以用max指定上限},created() {// 用于初始化緩存虛擬DOM數組和vnode的keythis.cache = Object.create(null)this.keys = []},destroyed() {// 銷毀緩存cache的組件實例for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)}},mounted() {// prune 削減精簡[v.]// 去監控include和exclude的改變,根據最新的include和exclude的內容,來實時削減緩存的組件的內容this.$watch('include', (val) => {pruneCache(this, (name) => matches(val, name))})this.$watch('exclude', (val) => {pruneCache(this, (name) => !matches(val, name))})},
}
render函數
1.會在 keep-alive 組件內部去寫自己的內容,所以可以去獲取默認 slot 的內容,然后根據這個去獲取組件
2.keep-alive 只對第一個組件有效,所以獲取第一個子組件。
3.和 keep-alive 搭配使用的一般有:動態組件 和router-view
render () {//function getFirstComponentChild (children: ?Array<VNode>): ?VNode {if (Array.isArray(children)) {for (let i = 0; i < children.length; i++) {const c = children[i]if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {return c}}}}const slot = this.$slots.default // 獲取默認插槽const vnode: VNode = getFirstComponentChild(slot)// 獲取第一個子組件const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 組件參數if (componentOptions) { // 是否有組件參數// check patternconst name: ?string = getComponentName(componentOptions) // 獲取組件名const { include, exclude } = thisif (// not included(include && (!name || !matches(include, name))) ||// excluded(exclude && name && matches(exclude, name))) {// 如果不匹配當前組件的名字和include以及exclude// 那么直接返回組件的實例return vnode}const { cache, keys } = this// 獲取這個組件的keyconst key: ?string = vnode.key == null// same constructor may get registered as different local components// so cid alone is not enough (#3269)? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.keyif (cache[key]) {// LRU緩存策略執行vnode.componentInstance = cache[key].componentInstance // 組件初次渲染的時候componentInstance為undefined// make current key freshestremove(keys, key)keys.push(key)// 根據LRU緩存策略執行,將key從原來的位置移除,然后將這個key值放到最后面} else {// 在緩存列表里面沒有的話,則加入,同時判斷當前加入之后,是否超過了max所設定的范圍,如果是,則去除// 使用時間間隔最長的一個cache[key] = vnodekeys.push(key)// prune oldest entryif (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)}}// 將組件的keepAlive屬性設置為truevnode.data.keepAlive = true // 作用:判斷是否要執行組件的created、mounted生命周期函數}return vnode || (slot && slot[0])
}
keep-alive 具體是通過 cache 數組緩存所有組件的 vnode 實例。當 cache 內原有組件被使用時會將該組件 key 從 keys 數組中刪除,然后 push 到 keys數組最后,以便清除最不常用組件。
實現步驟:
1.獲取 keep-alive 下第一個子組件的實例對象,通過他去獲取這個組件的組件名
2.通過當前組件名去匹配原來 include 和 exclude,判斷當前組件是否需要緩存,不3.需要緩存,直接返回當前組件的實例vNode需要緩存,判斷他當前是否在緩存數組里面:
(1)存在,則將他原來位置上的 key 給移除,同時將這個組件的 key 放到數組最后面(LRU)
(2)不存在,將組件 key 放入數組,然后判斷當前 key數組是否超過 max 所設置的范圍,超過,那么削減未使用時間最長的一個組件的 key
4.最后將這個組件的 keepAlive 設置為 true
(3)keep-alive 本身的創建過程和 patch 過程
緩存渲染的時候,會根據 vnode.componentInstance(首次渲染 vnode.componentInstance 為 undefined) 和 keepAlive 屬性判斷不會執行組件的 created、mounted 等鉤子函數,而是對緩存的組件執行 patch 過程∶ 直接把緩存的 DOM 對象直接插入到目標元素中,完成了數據更新的情況下的渲染過程。
組件的首次渲染∶判斷組件的 abstract 屬性,才往父組件里面掛載 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {const options = vm.$options// locate first non-abstract parentlet parent = options.parentif (parent && !options.abstract) { // 判斷組件的abstract屬性,才往父組件里面掛載DOMwhile (parent.$options.abstract && parent.$parent) {parent = parent.$parent}parent.$children.push(vm)}vm.$parent = parentvm.$root = parent ? parent.$root : vmvm.$children = []vm.$refs = {}vm._watcher = nullvm._inactive = nullvm._directInactive = falsevm._isMounted = falsevm._isDestroyed = falsevm._isBeingDestroyed = false
}
判斷當前 keepAlive 和 componentInstance 是否存在來判斷是否要執行組件 prepatch 還是執行創建 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {if (vnode.componentInstance &&!vnode.componentInstance._isDestroyed &&vnode.data.keepAlive) { // componentInstance在初次是undefined!!!// kept-alive components, treat as a patchconst mountedNode: any = vnode // work around flowcomponentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch函數執行的是組件更新的過程} else {const child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance)child.$mount(hydrating ? vnode.elm : undefined, hydrating)}},
prepatch 操作就不會在執行組件的 mounted 和 created 生命周期函數,而是直接將 DOM 插入
(4)LRU (least recently used)緩存策略
LRU 緩存策略∶ 從內存中找出最久未使用的數據并置換新的數據。
LRU(Least rencently used)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是 “如果數據最近被訪問過,那么將來被訪問的幾率也更高”。 最常見的實現是使用一個鏈表保存緩存數據,詳細算法實現如下∶
1.新數據插入到鏈表頭部
2.每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部
3.鏈表滿的時候,將鏈表尾部的數據丟棄。