<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>

          Vue源碼解析,keep-alive是如何實(shí)現(xiàn)緩存的?

          共 12985字,需瀏覽 26分鐘

           ·

          2020-09-14 21:23

          作者:WahFung

          原文鏈接:https://juejin.im/post/6862206197877964807

          前言

          在性能優(yōu)化上,最常見的手段就是緩存。對(duì)需要經(jīng)常訪問的資源進(jìn)行緩存,減少請(qǐng)求或者是初始化的過程,從而降低時(shí)間或內(nèi)存的消耗。Vue 為我們提供了緩存組件 keep-alive,它可用于路由級(jí)別或組件級(jí)別的緩存。

          但其中的緩存原理你是否了解,組件緩存渲染又是如何工作。那么本文就來解析 keep-alive 的原理。

          LRU策略

          在使用 keep-alive 時(shí),可以添加 prop 屬性 includeexcludemax 允許組件有條件的緩存。既然有限制條件,舊的組件需要?jiǎng)h除緩存,新的組件就需要加入到最新緩存,那么要如何制定對(duì)應(yīng)的策略?

          LRU(Least recently used,最近最少使用)策略根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù)。LRU 策略的設(shè)計(jì)原則是,如果一個(gè)數(shù)據(jù)在最近一段時(shí)間沒有被訪問到,那么在將來它被訪問的可能性也很小。也就是說,當(dāng)限定的空間已存滿數(shù)據(jù)時(shí),應(yīng)當(dāng)把最久沒有被訪問到的數(shù)據(jù)淘汰。

          1. 現(xiàn)在緩存最大只允許存3個(gè)組件,ABC三個(gè)組件依次進(jìn)入緩存,沒有任何問題
          2. 當(dāng)D組件被訪問時(shí),內(nèi)存空間不足,A是最早進(jìn)入也是最舊的組件,所以A組件從緩存中刪除,D組件加入到最新的位置
          3. 當(dāng)B組件被再次訪問時(shí),由于B還在緩存中,B移動(dòng)到最新的位置,其他組件相應(yīng)的往后一位
          4. 當(dāng)E組件被訪問時(shí),內(nèi)存空間不足,C變成最久未使用的組件,C組件從緩存中刪除,E組件加入到最新的位置

          keep-alive 緩存機(jī)制便是根據(jù)LRU策略來設(shè)置緩存組件新鮮度,將很久未訪問的組件從緩存中刪除。了解完緩存機(jī)制,接下來進(jìn)入源碼,看看keep-alive組件是如何實(shí)現(xiàn)的。

          組件實(shí)現(xiàn)原理

          //?源碼位置:src/core/components/keep-alive.js
          export?default?{
          ??name:?'keep-alive',
          ??abstract:?true,
          ??props:?{
          ????include:?patternTypes,
          ????exclude:?patternTypes,
          ????max:?[String,?Number]
          ??},
          ??created?()?{
          ????this.cache?=?Object.create(null)
          ????this.keys?=?[]
          ??},
          ??destroyed?()?{
          ????for?(const?key?in?this.cache)?{
          ??????pruneCacheEntry(this.cache,?key,?this.keys)
          ????}
          ??},
          ??mounted?()?{
          ????this.$watch('include',?val?=>?{
          ??????pruneCache(this,?name?=>?matches(val,?name))
          ????})
          ????this.$watch('exclude',?val?=>?{
          ??????pruneCache(this,?name?=>?!matches(val,?name))
          ????})
          ??},
          ??render?()?{
          ????const?slot?=?this.$slots.default
          ????const?vnode:?VNode?=?getFirstComponentChild(slot)
          ????const?componentOptions:??VNodeComponentOptions?=?vnode?&&?vnode.componentOptions
          ????if?(componentOptions)?{
          ??????//?check?pattern
          ??????const?name:??string?=?getComponentName(componentOptions)
          ??????const?{?include,?exclude?}?=?this
          ??????if?(
          ????????//?not?included
          ????????(include?&&?(!name?||?!matches(include,?name)))?||
          ????????//?excluded
          ????????(exclude?&&?name?&&?matches(exclude,?name))
          ??????)?{
          ????????return?vnode
          ??????}

          ??????const?{?cache,?keys?}?=?this
          ??????const?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.key
          ??????if?(cache[key])?{
          ????????vnode.componentInstance?=?cache[key].componentInstance
          ????????//?make?current?key?freshest
          ????????remove(keys,?key)
          ????????keys.push(key)
          ??????}?else?{
          ????????cache[key]?=?vnode
          ????????keys.push(key)
          ????????//?prune?oldest?entry
          ????????if?(this.max?&&?keys.length?>?parseInt(this.max))?{
          ??????????pruneCacheEntry(cache,?keys[0],?keys,?this._vnode)
          ????????}
          ??????}
          ??????vnode.data.keepAlive?=?true
          ????}
          ????return?vnode?||?(slot?&&?slot[0])
          ??}
          }

          kepp-alive 實(shí)際是一個(gè)抽象組件,只對(duì)包裹的子組件做處理,并不會(huì)和子組件建立父子關(guān)系,也不會(huì)作為節(jié)點(diǎn)渲染到頁面上。在組件開頭就設(shè)置 abstracttrue,代表該組件是一個(gè)抽象組件。

          //?源碼位置:src/core/instance/lifecycle.js
          export?function?initLifecycle?(vm:?Component)?{
          ??const?options?=?vm.$options

          ??//?locate?first?non-abstract?parent
          ??let?parent?=?options.parent
          ??if?(parent?&&?!options.abstract)?{
          ????while?(parent.$options.abstract?&&?parent.$parent)?{
          ??????parent?=?parent.$parent
          ????}
          ????parent.$children.push(vm)
          ??}
          ??vm.$parent?=?parent
          ??//?...
          }

          那么抽象組件是如何忽略這層關(guān)系的呢?在初始化階段會(huì)調(diào)用 initLifecycle,里面判斷父級(jí)是否為抽象組件,如果是抽象組件,就選取抽象組件的上一級(jí)作為父級(jí),忽略與抽象組件和子組件之間的層級(jí)關(guān)系。

          回到 keep-alive 組件,組件是沒有編寫 template 模板,而是由 render 函數(shù)決定渲染結(jié)果。

          const?slot?=?this.$slots.default
          const?vnode:?VNode?=?getFirstComponentChild(slot)

          如果 keep-alive 存在多個(gè)子元素,keep-alive 要求同時(shí)只有一個(gè)子元素被渲染。所以在開頭會(huì)獲取插槽內(nèi)的子元素,調(diào)用 getFirstComponentChild 獲取到第一個(gè)子元素的 VNode

          //?check?pattern
          const?name:??string?=?getComponentName(componentOptions)
          const?{?include,?exclude?}?=?this
          if?(
          ??//?not?included
          ??(include?&&?(!name?||?!matches(include,?name)))?||
          ??//?excluded
          ??(exclude?&&?name?&&?matches(exclude,?name))
          )?{
          ??return?vnode
          }

          function?matches?(pattern:?string?|?RegExp?|?Array,?name:?string):?boolean?{
          ??if?(Array.isArray(pattern))?{
          ????return?pattern.indexOf(name)?>?-1
          ??}?else?if?(typeof?pattern?===?'string')?{
          ????return?pattern.split(',').indexOf(name)?>?-1
          ??}?else?if?(isRegExp(pattern))?{
          ????return?pattern.test(name)
          ??}
          ??return?false
          }

          接著判斷當(dāng)前組件是否符合緩存條件,組件名與include不匹配或與exclude匹配都會(huì)直接退出并返回 VNode,不走緩存機(jī)制。

          const?{?cache,?keys?}?=?this
          const?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.key
          if?(cache[key])?{
          ??vnode.componentInstance?=?cache[key].componentInstance
          ??//?make?current?key?freshest
          ??remove(keys,?key)
          ??keys.push(key)
          }?else?{
          ??cache[key]?=?vnode
          ??keys.push(key)
          ??//?prune?oldest?entry
          ??if?(this.max?&&?keys.length?>?parseInt(this.max))?{
          ????pruneCacheEntry(cache,?keys[0],?keys,?this._vnode)
          ??}
          }
          vnode.data.keepAlive?=?true

          匹配條件通過會(huì)進(jìn)入緩存機(jī)制的邏輯,如果命中緩存,從 cache 中獲取緩存的實(shí)例設(shè)置到當(dāng)前的組件上,并調(diào)整 key 的位置將其放到最后。如果沒命中緩存,將當(dāng)前 VNode 緩存起來,并加入當(dāng)前組件的 key。如果緩存組件的數(shù)量超出 max 的值,即緩存空間不足,則調(diào)用 pruneCacheEntry 將最舊的組件從緩存中刪除,即 keys[0] 的組件。之后將組件的 keepAlive 標(biāo)記為 true,表示它是被緩存的組件。

          function?pruneCacheEntry?(
          ??cache:?VNodeCache,
          ??key:?string,
          ??keys:?Array,
          ??current?:?VNode
          )?
          {
          ??const?cached?=?cache[key]
          ??if?(cached?&&?(!current?||?cached.tag?!==?current.tag))?{
          ????cached.componentInstance.$destroy()
          ??}
          ??cache[key]?=?null
          ??remove(keys,?key)
          }

          pruneCacheEntry 負(fù)責(zé)將組件從緩存中刪除,它會(huì)調(diào)用組件 $destroy 方法銷毀組件實(shí)例,緩存組件置空,并移除對(duì)應(yīng)的 key

          mounted?()?{
          ??this.$watch('include',?val?=>?{
          ????pruneCache(this,?name?=>?matches(val,?name))
          ??})
          ??this.$watch('exclude',?val?=>?{
          ????pruneCache(this,?name?=>?!matches(val,?name))
          ??})
          }

          function?pruneCache?(keepAliveInstance:?any,?filter:?Function)?{
          ??const?{?cache,?keys,?_vnode?}?=?keepAliveInstance
          ??for?(const?key?in?cache)?{
          ????const?cachedNode:??VNode?=?cache[key]
          ????if?(cachedNode)?{
          ??????const?name:??string?=?getComponentName(cachedNode.componentOptions)
          ??????if?(name?&&?!filter(name))?{
          ????????pruneCacheEntry(cache,?key,?keys,?_vnode)
          ??????}
          ????}
          ??}
          }

          keep-alivemounted 會(huì)監(jiān)聽 includeexclude 的變化,屬性發(fā)生改變時(shí)調(diào)整緩存和 keys 的順序,最終調(diào)用的也是 pruneCacheEntry

          小結(jié)cache 用于緩存組件,keys 存儲(chǔ)組件的 key,根據(jù)LRU策略來調(diào)整緩存組件。keep-aliverender 中最后會(huì)返回組件的 VNode,因此我們也可以得出一個(gè)結(jié)論,keep-alive 并非真的不會(huì)渲染,而是渲染的對(duì)象是包裹的子組件。

          組件渲染流程

          溫馨提示:這部分內(nèi)容需要對(duì) renderpatch 過程有了解

          渲染過程最主要的兩個(gè)過程就是 renderpatch,在 render 之前還會(huì)有模板編譯,render 函數(shù)就是模板編譯后的產(chǎn)物,它負(fù)責(zé)構(gòu)建 VNode 樹,構(gòu)建好的 VNode 會(huì)傳遞給 patchpatch 根據(jù) VNode 的關(guān)系生成真實(shí)dom節(jié)點(diǎn)樹。

          這張圖描述了 Vue 視圖渲染的流程:??

          VNode構(gòu)建完成后,最終會(huì)被轉(zhuǎn)換成真實(shí)dom,而 patch 是必經(jīng)的過程。為了更好的理解組件渲染的過程,假設(shè) keep-alive 包括的組件有A和B兩個(gè)組件,默認(rèn)展示A組件。

          初始化渲染

          組件在 patch 過程是會(huì)執(zhí)行 createComponent 來掛載組件的,A組件也不例外。

          //?源碼位置:src/core/vdom/patch.js
          function?createComponent?(vnode,?insertedVnodeQueue,?parentElm,?refElm)?{
          ??let?i?=?vnode.data
          ??if?(isDef(i))?{
          ????const?isReactivated?=?isDef(vnode.componentInstance)?&&?i.keepAlive
          ????if?(isDef(i?=?i.hook)?&&?isDef(i?=?i.init))?{
          ??????i(vnode,?false?/*?hydrating?*/)
          ????}
          ????//?after?calling?the?init?hook,?if?the?vnode?is?a?child?component
          ????//?it?should've?created?a?child?instance?and?mounted?it.?the?child
          ????//?component?also?has?set?the?placeholder?vnode's?elm.
          ????//?in?that?case?we?can?just?return?the?element?and?be?done.
          ????if?(isDef(vnode.componentInstance))?{
          ??????initComponent(vnode,?insertedVnodeQueue)
          ??????insert(parentElm,?vnode.elm,?refElm)
          ??????if?(isTrue(isReactivated))?{
          ????????reactivateComponent(vnode,?insertedVnodeQueue,?parentElm,?refElm)
          ??????}
          ??????return?true
          ????}
          ??}
          }

          isReactivated 標(biāo)識(shí)組件是否重新激活。在初始化渲染時(shí),A組件還沒有初始化構(gòu)造完成,componentInstance 還是 undefined。而A組件的 keepAlivetrue,因?yàn)?keep-alive 作為父級(jí)包裹組件,會(huì)先于A組件掛載,也就是 kepp-alive 會(huì)先執(zhí)行 render 的過程,A組件被緩存起來,之后對(duì)插槽內(nèi)第一個(gè)組件(A組件)的 keepAlive 賦值為 true,不記得這個(gè)過程請(qǐng)看上面組件實(shí)現(xiàn)的代碼。所以此時(shí)的 isReactivatedfalse

          接著會(huì)調(diào)用 init 函數(shù)進(jìn)行組件初始化,它是組件的一個(gè)鉤子函數(shù):

          //?源碼位置:src/core/vdom/create-component.js
          const?componentVNodeHooks?=?{
          ??init?(vnode:?VNodeWithData,?hydrating:?boolean):??boolean?{
          ????if?(
          ??????vnode.componentInstance?&&
          ??????!vnode.componentInstance._isDestroyed?&&
          ??????vnode.data.keepAlive
          ????)?{
          ??????//?kept-alive?components,?treat?as?a?patch
          ??????const?mountedNode:?any?=?vnode?//?work?around?flow
          ??????componentVNodeHooks.prepatch(mountedNode,?mountedNode)
          ????}?else?{
          ??????const?child?=?vnode.componentInstance?=?createComponentInstanceForVnode(
          ????????vnode,
          ????????activeInstance
          ??????)
          ??????child.$mount(hydrating???vnode.elm?:?undefined,?hydrating)
          ????}
          ??},
          ??//?...
          }

          createComponentInstanceForVnode 內(nèi)會(huì) new Vue 構(gòu)造組件實(shí)例并賦值到 componentInstance,隨后調(diào)用 $mount 掛載組件。

          createComponent,繼續(xù)走下面的邏輯:

          if?(isDef(vnode.componentInstance))?{
          ??initComponent(vnode,?insertedVnodeQueue)
          ??insert(parentElm,?vnode.elm,?refElm)
          ??if?(isTrue(isReactivated))?{
          ????reactivateComponent(vnode,?insertedVnodeQueue,?parentElm,?refElm)
          ??}
          ??return?true
          }

          調(diào)用 initComponentvnode.elm 賦值為真實(shí)dom,然后調(diào)用 insert 將組件的真實(shí)dom插入到父元素中。

          所以在初始化渲染中,keep-alive 將A組件緩存起來,然后正常的渲染A組件。

          緩存渲染

          當(dāng)切換到B組件,再切換回A組件時(shí),A組件命中緩存被重新激活。

          再次經(jīng)歷 patch 過程,keep-alive 是根據(jù)插槽獲取當(dāng)前的組件,那么插槽的內(nèi)容又是如何更新實(shí)現(xiàn)緩存?

          const?isRealElement?=?isDef(oldVnode.nodeType)
          if?(!isRealElement?&&?sameVnode(oldVnode,?vnode))?{
          ??//?patch?existing?root?node
          ??patchVnode(oldVnode,?vnode,?insertedVnodeQueue,?null,?null,?removeOnly)
          }

          非初始化渲染時(shí),patch 會(huì)調(diào)用 patchVnode 對(duì)比新舊節(jié)點(diǎn)。

          //?源碼位置:src/core/vdom/patch.js
          function?patchVnode?(
          ??oldVnode,
          ??vnode,
          ??insertedVnodeQueue,
          ??ownerArray,
          ??index,
          ??removeOnly
          )?
          {
          ??//?...
          ??let?i
          ??const?data?=?vnode.data
          ??if?(isDef(data)?&&?isDef(i?=?data.hook)?&&?isDef(i?=?i.prepatch))?{
          ????i(oldVnode,?vnode)
          ??}
          ??//?...
          }

          patchVnode 內(nèi)會(huì)調(diào)用鉤子函數(shù) prepatch

          //?源碼位置:src/core/vdom/create-component.js
          prepatch?(oldVnode:?MountedComponentVNode,?vnode:?MountedComponentVNode)?{
          ??const?options?=?vnode.componentOptions
          ??const?child?=?vnode.componentInstance?=?oldVnode.componentInstance
          ??updateChildComponent(
          ????child,
          ????options.propsData,?//?updated?props
          ????options.listeners,?//?updated?listeners
          ????vnode,?//?new?parent?vnode
          ????options.children?//?new?children
          ??)
          },

          updateChildComponent 就是更新的關(guān)鍵方法,它里面主要是更新實(shí)例的一些屬性:

          //?源碼位置:src/core/instance/lifecycle.js
          export?function?updateChildComponent?(
          ??vm:?Component,
          ??propsData:??Object,
          ??listeners:??Object,
          ??parentVnode:?MountedComponentVNode,
          ??renderChildren:??Array
          )?
          {
          ??//?...

          ??//?Any?static?slot?children?from?the?parent?may?have?changed?during?parent's
          ??//?update.?Dynamic?scoped?slots?may?also?have?changed.?In?such?cases,?a?forced
          ??//?update?is?necessary?to?ensure?correctness.
          ??const?needsForceUpdate?=?!!(
          ????renderChildren?||???????????????//?has?new?static?slots
          ????vm.$options._renderChildren?||??//?has?old?static?slots
          ????hasDynamicScopedSlot
          ??)
          ??
          ??//?...
          ??
          ??//?resolve?slots?+?force?update?if?has?children
          ??if?(needsForceUpdate)?{
          ????vm.$slots?=?resolveSlots(renderChildren,?parentVnode.context)
          ????vm.$forceUpdate()
          ??}
          }

          Vue.prototype.$forceUpdate?=?function?()?{
          ??const?vm:?Component?=?this
          ??if?(vm._watcher)?{
          ????//?這里最終會(huì)執(zhí)行?vm._update(vm._render)
          ????vm._watcher.update()
          ??}
          }

          從注釋中可以看到 needsForceUpdate 是有插槽才會(huì)為 truekeep-alive 符合條件。首先調(diào)用 resolveSlots 更新 keep-alive 的插槽,然后調(diào)用 $forceUpdatekeep-alive 重新渲染,再走一遍 render。因?yàn)锳組件在初始化已經(jīng)緩存了,keep-alive 直接返回緩存好的A組件 VNodeVNode 準(zhǔn)備好后,又來到了 patch 階段。

          function?createComponent?(vnode,?insertedVnodeQueue,?parentElm,?refElm)?{
          ??let?i?=?vnode.data
          ??if?(isDef(i))?{
          ????const?isReactivated?=?isDef(vnode.componentInstance)?&&?i.keepAlive
          ????if?(isDef(i?=?i.hook)?&&?isDef(i?=?i.init))?{
          ??????i(vnode,?false?/*?hydrating?*/)
          ????}
          ????//?after?calling?the?init?hook,?if?the?vnode?is?a?child?component
          ????//?it?should've?created?a?child?instance?and?mounted?it.?the?child
          ????//?component?also?has?set?the?placeholder?vnode's?elm.
          ????//?in?that?case?we?can?just?return?the?element?and?be?done.
          ????if?(isDef(vnode.componentInstance))?{
          ??????initComponent(vnode,?insertedVnodeQueue)
          ??????insert(parentElm,?vnode.elm,?refElm)
          ??????if?(isTrue(isReactivated))?{
          ????????reactivateComponent(vnode,?insertedVnodeQueue,?parentElm,?refElm)
          ??????}
          ??????return?true
          ????}
          ??}
          }

          A組件再次經(jīng)歷 createComponent 的過程,調(diào)用 init

          const?componentVNodeHooks?=?{
          ??init?(vnode:?VNodeWithData,?hydrating:?boolean):??boolean?{
          ????if?(
          ??????vnode.componentInstance?&&
          ??????!vnode.componentInstance._isDestroyed?&&
          ??????vnode.data.keepAlive
          ????)?{
          ??????//?kept-alive?components,?treat?as?a?patch
          ??????const?mountedNode:?any?=?vnode?//?work?around?flow
          ??????componentVNodeHooks.prepatch(mountedNode,?mountedNode)
          ????}?else?{
          ??????const?child?=?vnode.componentInstance?=?createComponentInstanceForVnode(
          ????????vnode,
          ????????activeInstance
          ??????)
          ??????child.$mount(hydrating???vnode.elm?:?undefined,?hydrating)
          ????}
          ??},
          }

          這時(shí)將不再走 $mount 的邏輯,只調(diào)用 prepatch 更新實(shí)例屬性。所以在緩存組件被激活時(shí),不會(huì)執(zhí)行 createdmounted 的生命周期函數(shù)。

          回到 createComponent,此時(shí)的 isReactivatedtrue,調(diào)用 reactivateComponent:

          function?reactivateComponent?(vnode,?insertedVnodeQueue,?parentElm,?refElm)?{
          ??let?i
          ??//?hack?for?#4339:?a?reactivated?component?with?inner?transition
          ??//?does?not?trigger?because?the?inner?node's?created?hooks?are?not?called
          ??//?again.?It's?not?ideal?to?involve?module-specific?logic?in?here?but
          ??//?there?doesn't?seem?to?be?a?better?way?to?do?it.
          ??let?innerNode?=?vnode
          ??while?(innerNode.componentInstance)?{
          ????innerNode?=?innerNode.componentInstance._vnode
          ????if?(isDef(i?=?innerNode.data)?&&?isDef(i?=?i.transition))?{
          ??????for?(i?=?0;?i?????????cbs.activate[i](emptyNode,?innerNode)
          ??????}
          ??????insertedVnodeQueue.push(innerNode)
          ??????break
          ????}
          ??}
          ??//?unlike?a?newly?created?component,
          ??//?a?reactivated?keep-alive?component?doesn't?insert?itself
          ??insert(parentElm,?vnode.elm,?refElm)
          }

          最后調(diào)用 insert 插入組件的dom節(jié)點(diǎn),至此緩存渲染流程完成。

          小結(jié):組件首次渲染時(shí),keep-alive 會(huì)將組件緩存起來。等到緩存渲染時(shí),keep-alive 會(huì)更新插槽內(nèi)容,之后 $forceUpdate 重新渲染。這樣在 render 時(shí)就獲取到最新的組件,如果命中緩存則從緩存中返回 VNode

          總結(jié)

          keep-alive 組件是抽象組件,在對(duì)應(yīng)父子關(guān)系時(shí)會(huì)跳過抽象組件,它只對(duì)包裹的子組件做處理,主要是根據(jù)LRU策略緩存組件 VNode,最后在 render 時(shí)返回子組件的 VNode。緩存渲染過程會(huì)更新 keep-alive 插槽,重新再 render 一次,從緩存中讀取之前的組件 VNode 實(shí)現(xiàn)狀態(tài)緩存。


          》》面試官都在用的題庫,快來看看《

          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  一级黄色免费看 | 成人高潮AA 毛片 | 久久九欧美三级日本三级 | 久久成人人人人精品欧 | 二级黄色视频 |