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

          Vue3最啰嗦的Reactivity數(shù)據(jù)響應式原理解析

          共 24391字,需瀏覽 49分鐘

           ·

          2021-05-27 13:56

          Vue3 如火如荼,與其干等,不如花一個下午茶的時間來看下最新的響應式數(shù)據(jù)是如何實現(xiàn)的吧。在本文中,會寫到 vue3 的依賴收集和 proxy 數(shù)據(jù)代理,以及副作用 (effect) 是如何進行工作的。

          基本差不多了,圖有點小丑,也可以看比人比較全的圖。QAQ

          前言

          好久沒有接觸Vue了,在前幾天觀看尤大的直播時談論對于看源碼的一些看法,是為了更好的上手vue? 還是想要學習內部的框架思想?

          國內前端:面試,面試會問。

          在大環(huán)境下似乎已經(jīng)卷到了只要你是開發(fā)者,那么必然需要去學習源碼,不論你是實習生,還是應屆生,或者是多年經(jīng)驗的老前端。

          如果你停滯下來,不跟著卷,那么忽然之間帶來的壓力就會將你沖垮,以至于你可能很難在內卷的環(huán)境下生存下去,哪怕你是對的。

          有興趣的話可以閱讀一下 @掘金泥石流大佬的寫的程序員焦慮程度自測表。

          似乎講了太多的題外話,與其發(fā)牢騷不如靜下心來,一起學習一下Reactivity的一些基本原理吧,相信閱讀完文章的你會對vue 3數(shù)據(jù)響應式有更加深刻的理解。

          而之所以選擇Reactivity模塊來說,是因為其耦合度較低,且是vue3.0核心模塊之一,性價比成本非常高。

          基礎篇

          在開始之前,如果不了解ES6出現(xiàn)的一些高階api,如,Proxy, Reflect, WeakMap, WeakSet,Map, Set等等可以自行翻閱到資源章節(jié),先了解前置知識點在重新觀看為最佳。

          Proxy

          @vue/reactivity中,Proxy是整個調度的基石。

          通過Proxy代理對象,才能夠在get, set方法中完成后續(xù)的事情,比如依賴收集,effect,track, trigger等等操作,在這里就不詳細展開,后續(xù)會詳細展開敘述。

          如果有同學迫不及待,加上天資聰慧,ES6有一定基礎,可以直接跳轉到原理篇進行觀看和思考。

          先來手寫一個簡單的Proxy。在其中handleCallback中寫了了set, get兩個方法,又來攔截當前屬性值變化的數(shù)據(jù)監(jiān)聽。先上代碼:

          const user = {
            name: 'wangly19',
            age: 22,
            description: '一名掉頭發(fā)微乎其微的前端小哥。'
          }

          const userProxy = new Proxy(user, {
            get(target, key) {
              console.log(`userProxy: 當前獲取key為${key}`)
              if (target.hasOwnProperty(key)) return target[key]
              return {
              }
            },
            set(target, key, value) {
              console.log(`userProxy: 當前設置值key為${key}, value為${value}`)
              let isWriteSuccess = false
              if (target.hasOwnProperty(key)) {
                target[key] = value
                isWriteSuccess = true
              }
              return isWriteSuccess
            }
          })

          console.log('myNaame', userProxy.name)

          userProxy.age = 23
          復制代碼

          當我們在對值去進行賦值修改和打印的時候,分別觸發(fā)了當前的setget方法。

          這一點非常重要,對于其他的一些屬性和使用方法在這里就不過多的贅述,

          Reflect

          Reflect并不是一個類,是一個內置的對象。這一點呢大家要知悉,不要直接實例化(new)使用,它的功能比較和Proxyhandles有點類似,在這一點基礎上又添加了很多Object的方法。

          在這里我們不去深究Reflect, 如果想要深入了解功能的同學,可以在后續(xù)資源中找到對應地址進行學習。在本章主要介紹了通過Reflect安全的操作對象。

          以下是對user對象的一些修改操作的實例,可以參考一下,在后續(xù)可能會用到。

          const user = {
            name: 'wangly19',
            age: 22,
            description: '一名掉頭發(fā)微乎其微的前端小哥。'
          }

          console.log('change age before' , Reflect.get(user, 'age'))

          const hasChange = Reflect.set(user, 'age', 23)
          console.log('set user age is done? ', hasChange ? 'yes' : 'no')

          console.log('change age after' , Reflect.get(user, 'age'))

          const hasDelete = Reflect.deleteProperty(user, 'age')

          console.log('delete user age is done?', hasDelete ? 'yes' : 'none')

          console.log('delete age after' , Reflect.get(user, 'age'))
          復制代碼

          原理篇

          當了解了前置的一些知識后,就要開始@vue/reactivity的源碼解析篇章了。下面開始會以簡單的思路來實現(xiàn)一個基礎的reactivity,當你了解其本質原理后,你會對@vue/reactivity依賴收集(track)觸發(fā)更新(trigger),以及副作用(effect)究竟是什么工作。

          reactive

          reactivevue3中用于生成引用類型api

          const user = reactive({
            name: 'wangly19',
            age: 22,
            description: '一名掉頭發(fā)微乎其微的前端小哥。'
          })
          復制代碼

          那么往函數(shù)內部看看,reactive方法究竟做了什么?

          在內部,對傳入的對象進行了一個target的只讀判斷,如果你傳入的target是一個只讀代理的話,會直接返回掉。對于正常進行reactive的話則是返回了createReactiveObject方法的值。

          export function reactive(target: object) {
            // if trying to observe a readonly proxy, return the readonly version.
            if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
              return target
            }
            return createReactiveObject(
              target,
              false,
              mutableHandlers,
              mutableCollectionHandlers,
              reactiveMap
            )
          }
          復制代碼

          createReactiveObject

          createReactiveObject中,做的事情就是為target添加一個proxy代理。這是其核心,reactive最終拿到的是一個proxy代理,參考Proxy章節(jié)的簡單事例就可以知道reactive是如何進行工作的了,那么在來看下createReactiveObject做了一些什么事情。

          首先先判斷當前target的類型,如果不符合要求,直接拋出警告并且返回原來的值。

          if (!isObject(target)) {
              if (__DEV__) {
                console.warn(`value cannot be made reactive: ${String(target)}`)
              }
              return target
            }
          復制代碼

          其次判斷當前對象是否已經(jīng)被代理且并不是只讀的,那么本身就是一個代理對象,那么就沒有必要再去進行代理了,直接將其當作返回值返回,避免重復代理。

          if (
              target[ReactiveFlags.RAW] &&
              !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
            ) {
              return target
            }
          復制代碼

          對于這些判斷代碼來說,閱讀起來并不是很困難,注意if ()中判斷的條件,看看它做了一些什么動作即可。而createReactiveObject做的最重要的事情就是創(chuàng)建targetproxy, 并將其放到Map中記錄。

          而比較有意思的是其中對傳入的target調用了不同的proxy handle。那么就一起來看看handles中究竟干了一些什么吧。

          const proxy = new Proxy(
              target,
              targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
            )
            proxyMap.set(target, proxy)
            return proxy
          復制代碼

          handles 的類型

          在對象類型中,將ObjectArrayMap,Set, WeakMap,WeakSet區(qū)分開來了。它們調用的是不同的Proxy Handle

          • baseHandlers.tsObject & Array會調用此文件下的mutableHandlers對象作為Proxy Handle。
          • collectionHandlers.tsMap,Set, WeakMap,WeakSet會調用此文件下的mutableCollectionHandlers對象作為Proxy Handle
          /**
           * 對象類型判斷
           * @lineNumber 41
           */
          function targetTypeMap(rawType: string) {
            switch (rawType) {
              case 'Object':
              case 'Array':
                return TargetType.COMMON
              case 'Map':
              case 'Set':
              case 'WeakMap':
              case 'WeakSet':
                return TargetType.COLLECTION
              default:
                return TargetType.INVALID
            }
          }
          復制代碼

          會在new Proxy的根據(jù)返回的targetType判斷。

          const proxy = new Proxy(
            target,
            targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
          )
          復制代碼

          由于篇幅有限,下文中只舉例mutableHandlers當作分析的參考。當理解mutableHandlers后對于collectionHandlers只是時間的問題。

          Proxy Handle

          在上面說到了根據(jù)不同的Type調用不同的handle,那么一起來看看mutableHandlers究竟做了什么吧。

          在基礎篇中,都知道Proxy可以接收一個配置對象,其中我們演示了getset的屬性方法。而mutableHandlers就是何其相同意義的事情,在內部分別定義get, set, deleteProperty, has, oneKeys等多個屬性參數(shù),如果不知道什么含義的話,可以看下Proxy Mdn。在這里你需要理解被監(jiān)聽的數(shù)據(jù) 只要發(fā)生增查刪改后,絕大多數(shù)都會進入到對應的回執(zhí)通道里面。

          在這里,我們用簡單的get, set來進行簡單的模擬實例。

          function createGetter () {
              return (target, key, receiver) => {
                const result = Reflect.get(target, key, receiver)
                track(target, key)
                return result
              }
          }

          const get = /*#__PURE__*/ createGetter()

          function createSetter () {
            
            return (target, key, value, receiver) => {
              const oldValue = target[key]
            const result = Reflect.set(target, key, value, receiver)
            if (result && oldValue != value) {
              trigger(target, key)
            }
            return result
            }
          }
          復制代碼

          get的時候會進行一個track的依賴收集,而set的時候則是觸發(fā)trigger的觸發(fā)機制。在vue3,而triggertrack的話都是在我們effect.ts當中聲明的,那么接下來就來看看依賴收集響應觸發(fā)究竟做了一些什么吧。

          Effect

          對于整個 effect 模塊,將其分為三個部分來去閱讀:

          • effect:副作用函數(shù)
          • teack: 依賴收集,在proxy代理數(shù)據(jù)get時調用
          • trigger: 觸發(fā)響應,在proxy代理數(shù)據(jù)發(fā)生變化的時候調用。

          effect

          通過一段實例來看下effect的使用,并且了解它主要參數(shù)是一個函數(shù)。在函數(shù)內部會幫你執(zhí)行一些副作用記錄和特性判斷。

          effect(() => {
              proxy.user = 1
          })
          復制代碼

          來看看vueeffect干了什么?

          在這里,首先判斷當前參數(shù)fn是否是一個effect,如果是的話就將raw中存放的fn進行替換。然后重新進行createReactiveEffect生成。

          export function effect<T = any>(
            fn: () => T,
            options: ReactiveEffectOptions = EMPTY_OBJ
          ): ReactiveEffect<T> {
            if (isEffect(fn)) {
              fn = fn.raw
            }
            const effect = createReactiveEffect(fn, options)
            if (!options.lazy) {
              effect()
            }
            return effect
          }

          復制代碼

          createReactiveEffect會將我們effect推入到effectStack中進行入棧操作,然后用activeEffect進行存取當前執(zhí)行的effect,在執(zhí)行完后會將其進行出棧。同時替換activeEffect為新的棧頂。

          而在effect執(zhí)行的過程中就會觸發(fā)proxy handle然后tracktrigger兩個關鍵的函數(shù)。

          function createReactiveEffect<T = any>(
            fn: () => T,
            options: ReactiveEffectOptions
          ): ReactiveEffect<T> {
            const effect = function reactiveEffect(): unknown {
              if (!effect.active) {
                return options.scheduler ? undefined : fn()
              }
              if (!effectStack.includes(effect)) {
                cleanup(effect)
                try {
                  enableTracking()
                  effectStack.push(effect)
                  activeEffect = effect
                  return fn()
                } finally {
                  effectStack.pop()
                  resetTracking()
                  activeEffect = effectStack[effectStack.length - 1]
                }
              }
            } as ReactiveEffect
            effect.id = uid++
            effect.allowRecurse = !!options.allowRecurse
            effect._isEffect = true
            effect.active = true
            effect.raw = fn
            effect.deps = []
            effect.options = options
            return effect
          }
          復制代碼

          來看一個簡版的effect,拋開大多數(shù)代碼包袱,下面的代碼是不是清晰很多。

          function effect(eff) {
            try {
              effectStack.push(eff)
              activeEffect = eff
              return eff(...argsument)
              
            } finally {
              effectStack.pop()
              activeEffect = effectStack[effectStack.length  - 1]
            }
          }
          復制代碼

          track(依賴收集)

          track的時候,會進行我們所熟知的依賴收集,會將當前activeEffect添加到dep里面,而說起這一類的關系。它會有一個一對多對多的關系。

          從代碼看也非常的清晰,首先我們會有一個一個總的targetMap它是一個WeakMapkeytarget(代理的對象), value是一個Map,稱之為depsMap,它是用于管理當前target中每個keydeps也就是副作用依賴,也就是以前熟知的depend。在vue3中是通過Set來去實現(xiàn)的。

          第一步先憑借當前target獲取targetMap中的depsMap,如果不存在就進行targetMap.set(target, (depsMap = new Map()))初始化聲明,其次就是從depsMap中拿當前keydeps, 如果沒有找到的話,同樣是使用depsMap.set(key, (dep = new Set()))進行初始化聲明,最后將當前activeEffect推入到deps, 進行依賴收集。


            1. targetMap中找target

            1. depsMap中找key

            1. activeEffect保存到dep里面。

          這樣的話就會形成一個一對多對多的結構模式,里面存放的是所有被proxy劫持的依賴。

          function track(target: object, type: TrackOpTypes, key: unknown) {
            if (!shouldTrack || activeEffect === undefined) {
              return
            }
            let depsMap = targetMap.get(target)
            if (!depsMap) {
              targetMap.set(target, (depsMap = new Map()))
            }
            let dep = depsMap.get(key)
            if (!dep) {
              depsMap.set(key, (dep = new Set()))
            }
            if (!dep.has(activeEffect)) {
              dep.add(activeEffect)
              activeEffect.deps.push(dep)
              if (__DEV__ && activeEffect.options.onTrack) {
                activeEffect.options.onTrack({
                  effect: activeEffect,
                  target,
                  type,
                  key
                })
              }
            }
          }
          復制代碼

          trigger(響應觸發(fā))

          trigger的時候,做的事情其實就是觸發(fā)當前響應依賴的執(zhí)行。

          首先,需要獲取當前key下所有渠道的deps,所以會看到有一個effectsadd函數(shù), 做的事情非常的簡單,就是來判斷當前傳入的depsMap的屬性是否需要添加到effects里面,在這里的條件就是effect不能是當前的activeEffecteffect.allowRecurse,來確保當前set key的依賴都進行執(zhí)行。

          const effects = new Set<ReactiveEffect>()
            const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
              if (effectsToAdd) {
                effectsToAdd.forEach(effect => {
                  if (effect !== activeEffect || effect.allowRecurse) {
                    effects.add(effect)
                  }
                })
              }
            }
          復制代碼

          下面下面熟知的場景就是判斷當前傳入的一些變化行為,最常見的就是在trigger中會傳遞的TriggerOpTypes行為,然后執(zhí)行add方法將其將符合條件的effect添加到effects當中去,在這里@vue/reactivity做了很多數(shù)據(jù)就變異上的行為,如length變化。

          然后根據(jù)不同的TriggerOpTypes進行depsMap的數(shù)據(jù)取出,最后放入effects。隨后通過run方法將當前的effect執(zhí)行,通過effects.forEach(run)進行執(zhí)行。

          if (type === TriggerOpTypes.CLEAR) {
              // collection being cleared
              // trigger all effects for target
              depsMap.forEach(add)
            } else if (key === 'length' && isArray(target)) {
              depsMap.forEach((dep, key) => {
                if (key === 'length' || key >= (newValue as number)) {
                  add(dep)
                }
              })
            } else {
              // schedule runs for SET | ADD | DELETE
              if (key !== void 0) {
                add(depsMap.get(key))
              }

              // also run for iteration key on ADD | DELETE | Map.SET
              switch (type) {
                case TriggerOpTypes.ADD:
                  if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY))
                    if (isMap(target)) {
                      add(depsMap.get(MAP_KEY_ITERATE_KEY))
                    }
                  } else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'))
                  }
                  break
                case TriggerOpTypes.DELETE:
                  if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY))
                    if (isMap(target)) {
                      add(depsMap.get(MAP_KEY_ITERATE_KEY))
                    }
                  }
                  break
                case TriggerOpTypes.SET:
                  if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY))
                  }
                  break
              }
            }
          復制代碼

          run又做了什么呢?

          首先就是判斷當前effectoptions下有沒有scheduler,如果有的話就使用schedule來處理執(zhí)行,反之直接直接執(zhí)行effect()

          if (effect.options.scheduler) {
                effect.options.scheduler(effect)
              } else {
                effect()
              }
          復制代碼

          將其縮短一點看處理邏輯,其實就是從targetMap中拿對應key的依賴。

          const depsMap = targetMap.get(target)
            if (!depsMap) {
              return
            }
            const dep = depsMap.get(key)
            if (dep) {
              dep.forEach((effect) => {
                effect()
              })
            }
          復制代碼

          Ref

          眾所周知,refvue3對普通類型的一個響應式數(shù)據(jù)聲明。而獲取ref的值需要通過ref.value的方式進行獲取,很多人以為ref就是一個簡單的reactive但其實不是。

          在源碼中,ref最終是調用一個createRef的方法,在其內部返回了RefImpl的實例。它與Proxy不同的是,ref的依賴收集和響應觸發(fā)是在getter/setter當中,這一點可以參考圖中demo形式,鏈接地址 gettter/setter。

          export function ref<T extends object>(value: T): ToRef<T>
          export function ref<T>(value: T): Ref<UnwrapRef<T>>
          export function ref<T = any>(): Ref<T | undefined>
          export function ref(value?: unknown) {
            return createRef(value)
          }

          function createRef(rawValue: unknown, shallow = false) {
            if (isRef(rawValue)) {
              return rawValue
            }
            return new RefImpl(rawValue, shallow)
          }
          復制代碼

          如圖所示,vuegetter中與proxy中的get一樣都調用了track收集依賴,在setter中進行_value值更改后調用trigger觸發(fā)器。

          class RefImpl<T> {
            private _value: T

            public readonly __v_isRef = true

            constructor(private _rawValue: T, public readonly _shallow = false) {
              this._value = _shallow ? _rawValue : convert(_rawValue)
            }

            get value() {
              track(toRaw(this), TrackOpTypes.GET, 'value')
              return this._value
            }

            set value(newVal) {
              if (hasChanged(toRaw(newVal), this._rawValue)) {
                this._rawValue = newVal
                this._value = this._shallow ? newVal : convert(newVal)
                trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
              }
            }
          }
          復制代碼

          那么你現(xiàn)在應該知道:

          • proxy handlereactive的原理,而ref的原理是getter/setter。
          • get的時候都調用了track,set的時候都調用了trigger
          • effect是數(shù)據(jù)響應的核心。

          Computed

          computed一般有兩種常見的用法, 一種是通過傳入一個對象,內部有setget方法,這種屬于ComputedOptions的形式。

          export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
          export function computed<T>(
            options: WritableComputedOptions<T>
          ): WritableComputedRef<T>
          export function computed<T>(
            getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
          )
          復制代碼

          而在內部會有getter / setter兩個變量來進行保存。

          getterOrOptions為函數(shù)的時候,會將其賦值給與getter。

          getterOrOptions為對象的時候,會將setget分別賦值給setter,getter。

          隨后將其作為參數(shù)進行實例化ComputedRefImpl類,并將其當作返回值返回出去。

          let getter: ComputedGetter<T>
            let setter: ComputedSetter<T>

            if (isFunction(getterOrOptions)) {
              getter = getterOrOptions
              setter = __DEV__
                ? () => {
                    console.warn('Write operation failed: computed value is readonly')
                  }
                : NOOP
            } else {
              getter = getterOrOptions.get
              setter = getterOrOptions.set
            }
            
            return new ComputedRefImpl(
              getter,
              setter,
              isFunction(getterOrOptions) || !getterOrOptions.set
            ) as any
          復制代碼

          那么ComputedRefImpl干了一些什么?

          計算屬性的源碼,其實絕大多數(shù)是依賴前面對effect的一些理解。

          首先,我們都知道,effect可以傳遞一個函數(shù)和一個對象options

          在這里將getter當作函數(shù)參數(shù)傳遞,也就是副作用,而在options當中配置了lazyscheduler。

          lazy表示effect并不會立即被執(zhí)行,而scheduler是在trigger中會判斷你是否傳入了scheduler,傳入后就執(zhí)行scheduler方法。

          而在computed scheduler當中,會判斷當前的_dirty是否為false,如果是的話會把_dirty設置為true,且執(zhí)行trigger觸發(fā)響應。

          class ComputedRefImpl<T> {
            private _value!: T
            private _dirty = true

            public readonly effect: ReactiveEffect<T>

            public readonly __v_isRef = true;
            public readonly [ReactiveFlags.IS_READONLY]: boolean

            constructor(
              getter: ComputedGetter<T>,
              private readonly _setter: ComputedSetter<T>,
              isReadonly: boolean
            ) {
              this.effect = effect(getter, {
                lazy: true,
                scheduler: () => {
                  if (!this._dirty) {
                    this._dirty = true
                    trigger(toRaw(this), TriggerOpTypes.SET, 'value')
                  }
                }
              })

              this[ReactiveFlags.IS_READONLY] = isReadonly
            }
          復制代碼

          而在getter/setter中會對_value進行不同操作。

          首先,在get value中,判斷當前._dirty是否為true,如果是的話執(zhí)行緩存的effect并將其返回結果存放到_value,并執(zhí)行track進行依賴收集。

          其次,在set value中,則是調用_setter方法重新新值。

          get value() {
              // the computed ref may get wrapped by other proxies e.g. readonly() #3376
              const self = toRaw(this)
              if (self._dirty) {
                self._value = this.effect()
                self._dirty = false
              }
              track(self, TrackOpTypes.GET, 'value')
              return self._value
            }

            set value(newValue: T) {
              this._setter(newValue)
            }
          復制代碼

          資源引用

          下面是一些參考資源,有興趣的小伙伴可以看下

          • ES6 系列之 WeakMap
          • Proxy 和 Reflect
          • Vue Mastery
          • Vue Docs
          • React 中引入 Vue3 的 @vue/reactivity 實現(xiàn)響應式狀態(tài)管理

          總結

          如果你使用vue的話強烈建議自己debug將這一塊看完,絕對會對你寫代碼有很大的幫助。vue3如火如荼,目前已經(jīng)有團隊作用于生產(chǎn)環(huán)境進行項目開發(fā),社區(qū)的生態(tài)也慢慢的發(fā)展起來。

          @vue/reactivity的閱讀難度并不高,也有很多優(yōu)質的教程,有一定的工作基礎和代碼知識都能循序漸進的理解下來。我個人其實并不需要將其理解的滾瓜爛熟,理解每一行代碼的意思什么的,而是了解其核心思想,學習框架理念以及一些框架開發(fā)者代碼寫法的思路。這都是能夠借鑒并將其吸收成為自己的知識。

          對于一個已經(jīng)轉到React生態(tài)體系下的前端來說,讀Vue的源碼其實更多的是豐富自己在思維上的知識,而不是為了面試而去讀的。正如同你背書不是為了考試,而是學習知識。在現(xiàn)在的環(huán)境下,很難做到這些事情,靜下心來專心理解一件知識不如背幾篇面經(jīng)。


          關注公眾號「前端Sharing」,持續(xù)為你推送精選好文。



          瀏覽 27
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  色呦呦在线视频 | 国产一区二区三区四区五区入口 | 国产无码久久久 | 永久免费 看片视频 | 中文字幕操逼网站 |