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

          深度:1.1萬字解讀Vue3.0源碼響應(yīng)式系統(tǒng)

          共 34513字,需瀏覽 70分鐘

           ·

          2021-04-06 19:11

          原文地址:https://hkc452.github.io/slamdunk-the-vue3/

          作者:KC

          effect 是響應(yīng)式系統(tǒng)的核心,而響應(yīng)式系統(tǒng)又是 vue3 中的核心,所以從 effect 開始講起。

          首先看下面 effect 的傳參,fn 是回調(diào)函數(shù),options 是傳入的參數(shù)。

          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
          }
          • 其中 option 的參數(shù)如下,都是屬于可選的。

          參數(shù) & 含義

          • lazy 是否延遲觸發(fā) effect
          • computed 是否為計算屬性
          • scheduler 調(diào)度函數(shù)
          • onTrack 追蹤時觸發(fā)
          • onTrigger 觸發(fā)回調(diào)時觸發(fā)
          • onStop 停止監(jiān)聽時觸發(fā)
          export interface ReactiveEffectOptions {
          lazy?: boolean
          computed?: boolean
          scheduler?: (job: ReactiveEffect) => void
          onTrack?: (event: DebuggerEvent) => void
          onTrigger?: (event: DebuggerEvent) => void
          onStop?: () => void
          }
          • 分析完參數(shù)之后,繼續(xù)我們一開始的分析。當(dāng)我們調(diào)用 effect 時,首先判斷傳入的 fn 是否是 effect,如果是,取出原始值,然后調(diào)用 createReactiveEffect 創(chuàng)建 新的effect, 如果傳入的 option 中的 lazy 不為為 true,則立即調(diào)用我們剛剛創(chuàng)建的 effect, 最后返回剛剛創(chuàng)建的 effect。

          • 那么createReactiveEffect是怎樣是創(chuàng)建 effect的呢?

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

          我們先忽略 reactiveEffect,繼續(xù)看下面的掛載的屬性。

          effect 掛載屬性 含義
          • id 自增id, 唯一標識effect

          • _isEffect 用于標識方法是否是effect

          • active effect 是否激活

          • raw 創(chuàng)建effect是傳入的fn

          • deps 持有當(dāng)前 effect 的dep 數(shù)組

          • options 創(chuàng)建effect是傳入的options

          • 回到 reactiveEffect,如果 effect 不是激活狀態(tài),這種情況發(fā)生在我們調(diào)用了 effect 中的 stop 方法之后,那么先前沒有傳入調(diào)用 scheduler 函數(shù)的話,直接調(diào)用原始方法fn,否則直接返回。

          • 那么處于激活狀態(tài)的 effect 要怎么進行處理呢?首先判斷是否當(dāng)前 effect 是否在 effectStack 當(dāng)中,如果在,則不進行調(diào)用,這個主要是為了避免死循環(huán)。拿下面測試用例來看

          it('should avoid infinite loops with other effects', () => {
          const nums = reactive({ num1: 0, num2: 1 })

          const spy1 = jest.fn(() => (nums.num1 = nums.num2))
          const spy2 = jest.fn(() => (nums.num2 = nums.num1))
          effect(spy1)
          effect(spy2)
          expect(nums.num1).toBe(1)
          expect(nums.num2).toBe(1)
          expect(spy1).toHaveBeenCalledTimes(1)
          expect(spy2).toHaveBeenCalledTimes(1)
          nums.num2 = 4
          expect(nums.num1).toBe(4)
          expect(nums.num2).toBe(4)
          expect(spy1).toHaveBeenCalledTimes(2)
          expect(spy2).toHaveBeenCalledTimes(2)
          nums.num1 = 10
          expect(nums.num1).toBe(10)
          expect(nums.num2).toBe(10)
          expect(spy1).toHaveBeenCalledTimes(3)
          expect(spy2).toHaveBeenCalledTimes(3)
          })
          • 如果不加 effectStack,會導(dǎo)致 num2 改變,觸發(fā)了 spy1, spy1 里面 num1 改變又觸發(fā)了 spy2, spy2 又會改變 num2,從而觸發(fā)了死循環(huán)。

          • 接著是清除依賴,每次 effect 運行都會重新收集依賴, deps 是持有 effect 的依賴數(shù)組,其中里面的每個 dep 是對應(yīng)對象某個 key 的 全部依賴,我們在這里需要做的就是首先把 effect 從 dep 中刪除,最后把 deps 數(shù)組清空。

          function cleanup(effect: ReactiveEffect) {
          const { deps } = effect
          if (deps.length) {
          for (let i = 0; i < deps.length; i++) {
          deps[i].delete(effect)
          }
          deps.length = 0
          }
          }
          • 清除完依賴,就開始重新收集依賴。首先開啟依賴收集,把當(dāng)前 effect 放入 effectStack 中,然后講 activeEffect 設(shè)置為當(dāng)前的 effect,activeEffect 主要為了在收集依賴的時候使用(在下面會很快講到),然后調(diào)用 fn 并且返回值,當(dāng)這一切完成的時候,finally 階段,會把當(dāng)前 effect 彈出,恢復(fù)原來的收集依賴的狀態(tài),還有恢復(fù)原來的 activeEffect。
           try {
          enableTracking()
          effectStack.push(effect)
          activeEffect = effect
          return fn(...args)
          } finally {
          effectStack.pop()
          resetTracking()
          activeEffect = effectStack[effectStack.length - 1]
          }
          • 那 effect 是怎么收集依賴的呢?vue3 利用 proxy 劫持對象,在上面運行 effect 中讀取對象的時候,當(dāng)前對象的 key 的依賴 set集合 會把 effect 收集進去。
          export function track(target: object, type: TrackOpTypes, key: unknown) {
          ...
          }
          • vue3 在 reactive 中觸發(fā) track 函數(shù),reactive 會在單獨的章節(jié)講。觸發(fā) track 的參數(shù)中,object 表示觸發(fā) track 的對象, type 代表觸發(fā) track 類型,而 key 則是 觸發(fā) track 的 object 的 key。在下面可以看到三種類型的讀取對象會觸發(fā) track,分別是 get、 has、 iterate。
          export const enum TrackOpTypes {
          GET = 'get',
          HAS = 'has',
          ITERATE = 'iterate'
          }
          • 回到 track 內(nèi)部,如果 shouldTrack 為 false 或者 activeEffect 為空,則不進行依賴收集。接著 targetMap 里面有沒有該對象,沒有新建 map,然后再看這個 map 有沒有這個對象的對應(yīng) key 的 依賴 set 集合,沒有則新建一個。如果對象對應(yīng)的 key 的 依賴 set 集合也沒有當(dāng)前 activeEffect, 則把 activeEffect 加到 set 里面,同時把 當(dāng)前 set 塞到 activeEffect 的 deps 數(shù)組。最后如果是開發(fā)環(huán)境而且傳入了 onTrack 函數(shù),則觸發(fā) onTrack。所以 deps 就是 effect 中所依賴的 key 對應(yīng)的 set 集合數(shù)組, 畢竟一般來說,effect 中不止依賴一個對象或者不止依賴一個對象的一個key,而且 一個對象可以能不止被一個 effect 使用,所以是 set 集合數(shù)組。
          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
          })
          }
          }
          • 依賴都收集完畢了,接下來就是觸發(fā)依賴。如果 targetMap 為空,說明這個對象沒有被追蹤,直接return。
          export function trigger(
          target: object,
          type: TriggerOpTypes,
          key?: unknown,
          newValue?: unknown,
          oldValue?: unknown,
          oldTarget?: Map<unknown, unknown> | Set<unknown>
          ) {
          const depsMap = targetMap.get(target)
          if (!depsMap) {
          // never been tracked
          return
          }
          ...
          }
          • 其中觸發(fā)的 type, 包括了 set、add、delete 和 clear。
          export const enum TriggerOpTypes {
          SET = 'set',
          ADD = 'add',
          DELETE = 'delete',
          CLEAR = 'clear'
          }
          • 接下來對 key 收集的依賴進行分組,computedRunners 具有更高的優(yōu)先級,會觸發(fā)下游的 effects 重新收集依賴,

          const effects = new Set() const computedRunners = new Set() add 方法是將 effect 添加進不同分組的函數(shù),其中 effect !== activeEffect 這個是為了避免死循環(huán),在下面的注釋也寫的很清楚,避免出現(xiàn) foo.value++ 這種情況。至于為什么是 set 呢,要避免 effect 多次運行。就好像循環(huán)中,set 觸發(fā)了 trigger ,那么 ITERATE 和 當(dāng)前 key 可能都屬于同個 effect,這樣就可以避免多次運行了。

          const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
          if (effectsToAdd) {
          effectsToAdd.forEach(effect => {
          if (effect !== activeEffect || !shouldTrack) {
          if (effect.options.computed) {
          computedRunners.add(effect)
          } else {
          effects.add(effect)
          }
          } else {
          // the effect mutated its own dependency during its execution.
          // this can be caused by operations like foo.value++
          // do not trigger or we end in an infinite loop
          }
          })
          }
          }
          • 下面根據(jù)觸發(fā) key 類型的不同進行 effect 的處理。如果是 clear 類型,則觸發(fā)這個對象所有的 effect。如果 key 是 length , 而且 target 是數(shù)組,則會觸發(fā) key 為 length 的 effects ,以及 key 大于等于新 length的 effects, 因為這些此時數(shù)組長度變化了。
          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)
          }
          })
          }
          • 下面則是對正常的新增、修改、刪除進行 effect 的分組, isAddOrDelete 表示新增 或者不是數(shù)組的刪除,這為了對迭代 key的 effect 進行觸發(fā),如果 isAddOrDelete 為 true 或者是 map 對象的設(shè)值,則觸發(fā) isArray(target) ? 'length' : ITERATE_KEY 的 effect ,如果 isAddOrDelete 為 true 且 對象為 map, 則觸發(fā) MAP_KEY_ITERATE_KEY 的 effect
          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
          const isAddOrDelete =
          type === TriggerOpTypes.ADD ||
          (type === TriggerOpTypes.DELETE && !isArray(target))
          if (
          isAddOrDelete ||
          (type === TriggerOpTypes.SET && target instanceof Map)
          ) {
          add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
          }
          if (isAddOrDelete && target instanceof Map) {
          add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
          }
          • 最后是運行 effect, 像上面所說的,computed effects 會優(yōu)先運行,因為 computed effects 在運行過程中,第一次會觸發(fā)上游把cumputed effect收集進去,再把下游 effect 收集起來。

          • 還有一點,就是 effect.options.scheduler,如果傳入了調(diào)度函數(shù),則通過 scheduler 函數(shù)去運行 effect, 但是 scheduler 里面可能不一定使用了 effect,例如 computed 里面,因為 computed 是延遲運行 effect, 這個會在講 computed 的時候再講。

          const run = (effect: ReactiveEffect) => {
          if (__DEV__ && effect.options.onTrigger) {
          effect.options.onTrigger({
          effect,
          target,
          key,
          type,
          newValue,
          oldValue,
          oldTarget
          })
          }
          if (effect.options.scheduler) {
          effect.options.scheduler(effect)
          } else {
          effect()
          }
          }

          // Important: computed effects must be run first so that computed getters
          // can be invalidated before any normal effects that depend on them are run.
          computedRunners.forEach(run)
          effects.forEach(run)
          • 可以發(fā)現(xiàn),不管是 track 還是 trigger, 都會導(dǎo)致 effect 重新運行去收集依賴。

          • 最后再講一個 stop 方法,當(dāng)我們調(diào)用 stop 方法后,會清空其他對象對 effect 的依賴,同時調(diào)用 onStop 回調(diào),最后將 effect 的激活狀態(tài)設(shè)置為 false

          export function stop(effect: ReactiveEffect) {
          if (effect.active) {
          cleanup(effect)
          if (effect.options.onStop) {
          effect.options.onStop()
          }
          effect.active = false
          }
          }
          • 這樣當(dāng)再一次調(diào)用 effect 的時候,不會進行依賴的重新收集,而且沒有調(diào)度函數(shù),就直接返回原始的 fn 的運行結(jié)果,否則直接返回 undefined。
          if (!effect.active) {
          return options.scheduler ? undefined : fn(...args)
          }

          reactive 是 vue3 中對數(shù)據(jù)進行劫持的核心,主要是利用了 Proxy 進行劫持,相比于 Object.defineproperty 能夠劫持的類型和范圍都更好,再也不用像 vue2 中那樣對數(shù)組進行類似 hack 方式的劫持了。

          • 下面快速看看 vue3 是怎么劫持。首先看看這個對象是是不是 __v_isReadonly 只讀的,這個枚舉在后面進行講述,如果是,直接返回,否者調(diào)用 createReactiveObject 進行創(chuàng)建。
          export function reactive(target: object) {
          // if trying to observe a readonly proxy, return the readonly version.
          if (target && (target as Target).__v_isReadonly) {
          return target
          }
          return createReactiveObject(
          target,
          false,
          mutableHandlers,
          mutableCollectionHandlers
          )
          }
          • createReactiveObject 中,有個四個參數(shù),target 就是我們需要傳入的對象,isReadonly 表示要創(chuàng)建的代理是不是只可讀的,baseHandlers 是對進行基本類型的劫持,即 [Object,Array] ,collectionHandlers 是對集合類型的劫持, 即 [Set, Map, WeakMap, WeakSet]。
          function createReactiveObject(
          target: Target,
          isReadonly: boolean,
          baseHandlers: ProxyHandler<any>,
          collectionHandlers: ProxyHandler<any>
          ) {
          if (!isObject(target)) {
          if (__DEV__) {
          console.warn(`value cannot be made reactive: ${String(target)}`)
          }
          return target
          }
          // target is already a Proxy, return it.
          // exception: calling readonly() on a reactive object
          if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
          return target
          }
          // target already has corresponding Proxy
          if (
          hasOwn(target, isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive)
          ) {
          return isReadonly ? target.__v_readonly : target.__v_reactive
          }
          // only a whitelist of value types can be observed.
          if (!canObserve(target)) {
          return target
          }
          const observed = new Proxy(
          target,
          collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
          )
          def(
          target,
          isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
          observed
          )
          return observed
          }
          • 如果我們傳入是 target 不是object,直接返回。而如果 target 已經(jīng)是個 proxy ,而且不是要求這個proxy 是已讀的,但這個 proxy 是個響應(yīng)式的,則直接返回這個 target。什么意思呢?我們創(chuàng)建的 proxy 有兩種類型,一種是響應(yīng)式的,另外一種是只讀的。

          • 而如果我們傳入的 target 上面有掛載了響應(yīng)式的 proxy,則直接返回上面掛載的 proxy 。

          • 如果上面都不滿足,則需要檢查一下我們傳進去的 target 是否可以進行劫持觀察,如果 target 上面掛載了 __v_skip 屬性 為 true 或者 不是我們再在上面講參數(shù)時候講的六種類型,或者 對象被freeze 了,還是不能進行劫持。

          const canObserve = (value: Target): boolean => {
          return (
          !value.__v_skip &&
          isObservableType(toRawType(value)) &&
          !Object.isFrozen(value)
          )
          }
          • 如果上面條件滿足,則進行劫持,可以看到我們會根據(jù) target 類型的不同進行不同的 handler,最后根據(jù)把 observed 掛載到原對象上,同時返回 observed。
           const observed = new Proxy(
          target,
          collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
          )
          def(
          target,
          isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
          observed
          )
          return observed
          • 現(xiàn)在繼續(xù)講講上面 ReactiveFlags 枚舉,skip 用于標記對象不可以進行代理,可以用于 創(chuàng)建 component 的時候,把options 進行 markRaw,isReactive 和 isReadonly 都是由 proxy 劫持返回值,表示 proxy 的屬性,raw 是 proxy 上面的 原始target ,reactive 和 readonly 是掛載在 target 上面的 proxy
          export const enum ReactiveFlags {
          skip = '__v_skip',
          isReactive = '__v_isReactive',
          isReadonly = '__v_isReadonly',
          raw = '__v_raw',
          reactive = '__v_reactive',
          readonly = '__v_readonly'
          }
          • 再講講可以創(chuàng)建的四種 proxy, 分別是reactive、 shallowReactive 、readonly 和 shallowReadonly。其實從字面意思就可以看出他們的區(qū)別了。具體細節(jié)會在 collectionHandlers 和 baseHandlers 進行講解

          baseHandlers 中主要包含四種 handler, mutableHandlers、readonlyHandlers、shallowReactiveHandlers、 shallowReadonlyHandlers。這里先介紹 mutableHandlers, 因為其他三種 handler 也算是 mutableHandlers 的變形版本。

          export const mutableHandlers: ProxyHandler<object> = {
          get,
          set,
          deleteProperty,
          has,
          ownKeys
          }
          • 從 mdn 上面可以看到,

            • handler.get() 方法用于攔截對象的讀取屬性操作。
            • handler.set() 方法是設(shè)置屬性值操作的捕獲器。
            • handler.deleteProperty() 方法用于攔截對對象屬性的 delete 操作。
            • handler.has() 方法是針對 in 操作符的代理方法。
            • handler.ownKeys() 方法用于攔截
            • Object.getOwnPropertyNames()
            • Object.getOwnPropertySymbols()
            • Object.keys()
            • for…in循環(huán)
          • 從下面可以看到 ownKeys 觸發(fā)時,主要追蹤 ITERATE 操作,has 觸發(fā)時,追蹤 HAS 操作,而 deleteProperty 觸發(fā)時,我們要看看是否刪除成功以及刪除的 key 是否是對象自身擁有的。

          function deleteProperty(target: object, key: string | symbol): boolean {
          const hadKey = hasOwn(target, key)
          const oldValue = (target as any)[key]
          const result = Reflect.deleteProperty(target, key)
          if (result && hadKey) {
          trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
          }
          return result
          }

          function has(target: object, key: string | symbol): boolean {
          const result = Reflect.has(target, key)
          track(target, TrackOpTypes.HAS, key)
          return result
          }

          function ownKeys(target: object): (string | number | symbol)[] {
          track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
          return Reflect.ownKeys(target)
          }
          • 接下來看看 set handler, set 函數(shù)通過 createSetter 工廠方法 進行創(chuàng)建,/#PURE/ 是為了 rollup tree shaking 的操作。

          • 對于非 shallow , 如果原來的對象不是數(shù)組, 舊值是 ref,新值不是 ref,則讓新的值 賦值給 ref.value , 讓 ref 去決定 trigger,這里不展開,ref 會在ref 章節(jié)展開。如果是 shallow ,管它三七二十一呢。

          const set = /*#__PURE__*/ createSetter()
          const shallowSet = /*#__PURE__*/ createSetter(true)
          function createSetter(shallow = false) {
          return function set(
          target: object,
          key: string | symbol,
          value: unknown,
          receiver: object
          ): boolean {
          const oldValue = (target as any)[key]
          if (!shallow) {
          value = toRaw(value)
          if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
          oldValue.value = value
          return true
          }
          } else {
          // in shallow mode, objects are set as-is regardless of reactive or not
          }

          ...
          return result
          }
          }
          • 接下來進行設(shè)置,需要注意的是,如果 target 是在原型鏈的值,那么 Reflect.set(target, key, value, receiver) 的設(shè)值值設(shè)置起作用的是 receiver 而不是 target,這也是什么在這種情況下不要觸發(fā) trigger 的原因。

          • 那么在 target === toRaw(receiver) 時,如果原來 target 上面有 key, 則觸發(fā) SET 操作,否則觸發(fā) ADD 操作。

              const hadKey = hasOwn(target, key)
          const result = Reflect.set(target, key, value, receiver)
          // don't trigger if target is something up in the prototype chain of original
          if (target === toRaw(receiver)) {
          if (!hadKey) {
          trigger(target, TriggerOpTypes.ADD, key, value)
          } else if (hasChanged(value, oldValue)) {
          trigger(target, TriggerOpTypes.SET, key, value, oldValue)
          }
          }
          • 接下來說說 get 操作,get 有四種,我們先拿其中一種說說。
          const get = /*#__PURE__*/ createGetter()
          const shallowGet = /*#__PURE__*/ createGetter(false, true)
          const readonlyGet = /*#__PURE__*/ createGetter(true)
          const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)

          function createGetter(isReadonly = false, shallow = false) {
          return function get(target: object, key: string | symbol, receiver: object) {
          ...


          const res = Reflect.get(target, key, receiver)

          if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
          return res
          }

          if (shallow) {
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return res
          }

          if (isRef(res)) {
          if (targetIsArray) {
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return res
          } else {
          // ref unwrapping, only for Objects, not for Arrays.
          return res.value
          }
          }

          !isReadonly && track(target, TrackOpTypes.GET, key)
          return isObject(res)
          ? isReadonly
          ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
          : reactive(res)
          : res
          }
          }
          • 首先如果 key 是 ReactiveFlags, 直接返回值,ReactiveFlags 的枚舉值在 reactive 中講過。
           if (key === ReactiveFlags.isReactive) {
          return !isReadonly
          } else if (key === ReactiveFlags.isReadonly) {
          return isReadonly
          } else if (key === ReactiveFlags.raw) {
          return target
          }
          • 而如果 target 是數(shù)組,而且調(diào)用了 ['includes', 'indexOf', 'lastIndexOf'] 這三個方法,則調(diào)用 arrayInstrumentations 進行獲取值,
          const targetIsArray = isArray(target)
          if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)
          }
          • arrayInstrumentations 中會觸發(fā)數(shù)組每一項值得 GET 追蹤,因為 一旦數(shù)組的變了,方法的返回值也會變,所以需要全部追蹤。對于 args 參數(shù),如果第一次調(diào)用返回失敗,會嘗試將 args 進行 toRaw 再調(diào)用一次。
          const arrayInstrumentations: Record<string, Function> = {}
          ;['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
          arrayInstrumentations[key] = function(...args: any[]): any {
          const arr = toRaw(this) as any
          for (let i = 0, l = (this as any).length; i < l; i++) {
          track(arr, TrackOpTypes.GET, i + '')
          }
          // we run the method using the original args first (which may be reactive)
          const res = arr[key](...args)
          if (res === -1 || res === false) {
          // if that didn't work, run it again using raw values.
          return arr[key](...args.map(toRaw))
          } else {
          return res
          }
          }
          })

          如果 key 是 Symbol ,而且也是 ecma 中 Symbol 內(nèi)置的 key 或者 key 是 獲取對象上面的原型,則直接返回 res 值。

          const res = Reflect.get(target, key, receiver)

          if (isSymbol(key) && builtInSymbols.has(key) || key === 'proto') { return res }

          • 而如果是 shallow 為 true,說明而且不是只讀的,則追蹤 GET 追蹤,這里可以看出,只讀不會進行追蹤。
          if (shallow) {
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return res
          }
          • 接下來都是針對非 shallow的。如果返回值是 ref,且 target 是數(shù)組,在非可讀的情況下,進行 Get 的 Track 操作,對于如果 target 是對象,則直接返回 ref.value,但是不會在這里觸發(fā) Get 操作,而是由 ref 內(nèi)部進行 track。
          if (isRef(res)) {
          if (targetIsArray) {
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return res
          } else {
          // ref unwrapping, only for Objects, not for Arrays.
          return res.value
          }
          }
          • 對于非只讀,我們還要根據(jù) key 進行 Track。而對于返回值,如果是對象,我們還要進行一層 wrap, 但這層是 lazy 的,也就是只有我們讀取到 key 的時候,才會讀下面的 值進行 reactive 包裝,這樣可以避免出現(xiàn)循環(huán)依賴而導(dǎo)致的錯誤,因為這樣就算里面有循環(huán)依賴也不怕,反正是延遲取值,而不會導(dǎo)致棧溢出。
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return isObject(res)
          ? isReadonly
          ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
          : reactive(res)
          : res
          • 這就是 mutableHandlers ,而對于 readonlyHandlers,我們可以看出首先不允許任何 set、 deleteProperty 操作,然后對于 get,我們剛才也知道,不會進行 track 操作。剩下兩個 shallowGet 和 shallowReadonlyGet,就不在講了。
          export const readonlyHandlers: ProxyHandler<object> = {
          get: readonlyGet,
          has,
          ownKeys,
          set(target, key) {
          if (__DEV__) {
          console.warn(
          `Set operation on key "${String(key)}" failed: target is readonly.`,
          target
          )
          }
          return true
          },
          deleteProperty(target, key) {
          if (__DEV__) {
          console.warn(
          `Delete operation on key "${String(key)}" failed: target is readonly.`,
          target
          )
          }
          return true
          }
          }

          collectionHandlers 主要是對 set、map、weakSet、weakMap 四種類型的對象進行劫持。主要有下面三種類型的 handler,當(dāng)然照舊,我們拿其中的 mutableCollectionHandlers 進行講解。剩余兩種結(jié)合理解。

          export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
          get: createInstrumentationGetter(false, false)
          }

          export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
          get: createInstrumentationGetter(false, false)(false, true)
          }

          export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
          get: createInstrumentationGetter(true, false)
          }
          • mutableCollectionHandlers 主要是對 collection 的方法進行劫持,所以主要是對 get 方法進行代理,接下來對 createInstrumentationGetter(false, false) 進行研究。

          • instrumentations 是代理 get 訪問的 handler,當(dāng)然如果我們訪問的 key 是 ReactiveFlags,直接返回存儲的值,否則如果訪問的 key 在 instrumentations 上,在由 instrumentations 進行處理。

          function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
          const instrumentations = shallow
          ? shallowInstrumentations
          : isReadonly
          ? readonlyInstrumentations
          : mutableInstrumentations

          return (
          target: CollectionTypes,
          key: string | symbol,
          receiver: CollectionTypes
          ) => {
          if (key === ReactiveFlags.isReactive) {
          return !isReadonly
          } else if (key === ReactiveFlags.isReadonly) {
          return isReadonly
          } else if (key === ReactiveFlags.raw) {
          return target
          }

          return Reflect.get(
          hasOwn(instrumentations, key) && key in target
          ? instrumentations
          : target,
          key,
          receiver
          )
          }
          }
          • 接下來看看 mutableInstrumentations ,可以看到 mutableInstrumentations 對常見集合的增刪改查以及 迭代方法進行了代理,我們就順著上面的 key 怎么進行攔截的。注意 this: MapTypes 是 ts 上對 this 類型進行標注
          const mutableInstrumentations: Record<string, Function> = {
          get(this: MapTypes, key: unknown) {
          return get(this, key, toReactive)
          },
          get size() {
          return size((this as unknown) as IterableCollections)
          },
          has,
          add,
          set,
          delete: deleteEntry,
          clear,
          forEach: createForEach(false, false)
          }
          const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
          iteratorMethods.forEach(method => {
          mutableInstrumentations[method as string] = createIterableMethod(
          method,
          false,
          false
          )
          readonlyInstrumentations[method as string] = createIterableMethod(
          method,
          true,
          false
          )
          shallowInstrumentations[method as string] = createIterableMethod(
          method,
          true,
          true
          )
          })
          • get 方法 首先獲取 target ,對 target 進行 toRaw, 這個會被 createInstrumentationGetter 中的 proxy 攔截返回原始的 target,然后對 key 也進行一次 toRaw, 如果兩者不一樣,說明 key 也是 reative 的, 對 key 和 rawkey 都進行 track ,然后調(diào)用 target 原型上面的 has 方法,如果 key 為 true ,調(diào)用 get 獲取值,同時對值進行 wrap ,對于 mutableInstrumentations 而言,就是 toReactive。
          function get(
          target: MapTypes,
          key: unknown,
          wrap: typeof toReactive | typeof toReadonly | typeof toShallow
          ) {
          target = toRaw(target)
          const rawKey = toRaw(key)
          if (key !== rawKey) {
          track(target, TrackOpTypes.GET, key)
          }
          track(target, TrackOpTypes.GET, rawKey)
          const { has, get } = getProto(target)
          if (has.call(target, key)) {
          return wrap(get.call(target, key))
          } else if (has.call(target, rawKey)) {
          return wrap(get.call(target, rawKey))
          }
          }
          • has 方法 跟 get 方法差不多,也是對 key 和 rawkey 進行 track。
          function has(this: CollectionTypes, key: unknown): boolean {
          const target = toRaw(this)
          const rawKey = toRaw(key)
          if (key !== rawKey) {
          track(target, TrackOpTypes.HAS, key)
          }
          track(target, TrackOpTypes.HAS, rawKey)
          const has = getProto(target).has
          return has.call(target, key) || has.call(target, rawKey)
          }
          • size 和 add 方法 size 最要是返回集合的大小,調(diào)用原型上的 size 方法,同時觸發(fā) ITERATE 類型的 track,而 add 方法添加進去之前要判斷原本是否已經(jīng)存在了,如果存在,則不會觸發(fā) ADD 類型的 trigger。
          function size(target: IterableCollections) {
          target = toRaw(target)
          track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
          return Reflect.get(getProto(target), 'size', target)
          }

          function add(this: SetTypes, value: unknown) {
          value = toRaw(value)
          const target = toRaw(this)
          const proto = getProto(target)
          const hadKey = proto.has.call(target, value)
          const result = proto.add.call(target, value)
          if (!hadKey) {
          trigger(target, TriggerOpTypes.ADD, value, value)
          }
          return result
          }

          set 方法

          • set 方法是針對 map 類型的,從 this 的類型我們就可以看出來了, 同樣這里我們也會對 key 做兩個校驗,第一,是看看現(xiàn)在 map 上面有沒有存在同名的 key,來決定是觸發(fā) SET 還是 ADD 的 trigger, 第二,對于開發(fā)環(huán)境,會進行 checkIdentityKeys 檢查
          function set(this: MapTypes, key: unknown, value: unknown) {
          value = toRaw(value)
          const target = toRaw(this)
          const { has, get, set } = getProto(target)

          let hadKey = has.call(target, key)
          if (!hadKey) {
          key = toRaw(key)
          hadKey = has.call(target, key)
          } else if (__DEV__) {
          checkIdentityKeys(target, has, key)
          }

          const oldValue = get.call(target, key)
          const result = set.call(target, key, value)
          if (!hadKey) {
          trigger(target, TriggerOpTypes.ADD, key, value)
          } else if (hasChanged(value, oldValue)) {
          trigger(target, TriggerOpTypes.SET, key, value, oldValue)
          }
          return result
          }
          • checkIdentityKeys 就是為了檢查目標對象上面,是不是同時存在 rawkey 和 key,因為這樣可能會數(shù)據(jù)不一致。
          function checkIdentityKeys(
          target: CollectionTypes,
          has: (key: unknown) => boolean,
          key: unknown
          ) {
          const rawKey = toRaw(key)
          if (rawKey !== key && has.call(target, rawKey)) {
          const type = toRawType(target)
          console.warn(
          `Reactive ${type} contains both the raw and reactive ` +
          `versions of the same object${type === `Map` ? `as keys` : ``}, ` +
          `which can lead to inconsistencies. ` +
          `Avoid differentiating between the raw and reactive versions ` +
          `of an object and only use the reactive version if possible.`
          )
          }
          }
          • deleteEntry 和 clear 方法
          • deleteEntry 主要是為了觸發(fā) DELETE trigger ,流程跟上面 set 方法差不多,而 clear 方法主要是觸發(fā) CLEAR track,但是里面做了一個防御性的操作,就是如果集合的長度已經(jīng)為0,則調(diào)用 clear 方法不會觸發(fā) trigger。
          function deleteEntry(this: CollectionTypes, key: unknown) {
          const target = toRaw(this)
          const { has, get, delete: del } = getProto(target)
          let hadKey = has.call(target, key)
          if (!hadKey) {
          key = toRaw(key)
          hadKey = has.call(target, key)
          } else if (__DEV__) {
          checkIdentityKeys(target, has, key)
          }

          const oldValue = get ? get.call(target, key) : undefined
          // forward the operation before queueing reactions
          const result = del.call(target, key)
          if (hadKey) {
          trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
          }
          return result
          }

          function clear(this: IterableCollections) {
          const target = toRaw(this)
          const hadItems = target.size !== 0
          const oldTarget = __DEV__
          ? target instanceof Map
          ? new Map(target)
          : new Set(target)
          : undefined
          // forward the operation before queueing reactions
          const result = getProto(target).clear.call(target)
          if (hadItems) {
          trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
          }
          return result
          }
          • forEach 方法 在調(diào)用 froEach 方法的時候會觸發(fā) ITERATE 類型的 track,需要注意 Size 方法也會同樣類型的 track,畢竟集合整體的變化會導(dǎo)致整個兩個方法的輸出不一樣。順帶提一句,還記得我們的 effect 時候的 trigger 嗎,對于 SET | ADD | DELETE 等類似的操作,因為會導(dǎo)致集合值得變化,所以也會觸發(fā) ITERATE_KEY 或則 MAP_KEY_ITERATE_KEY 的 effect 重新收集依賴。

          • 在調(diào)用原型上的 forEach 進行循環(huán)的時候,會對 key 和 value 都進行一層 wrap,對于我們來說,就是 reactive。

          function createForEach(isReadonly: boolean, shallow: boolean) {
          return function forEach(
          this: IterableCollections,
          callback: Function,
          thisArg?: unknown
          ) {
          const observed = this
          const target = toRaw(observed)
          const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
          !isReadonly && track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
          // important: create sure the callback is
          // 1. invoked with the reactive map as `this` and 3rd arg
          // 2. the value received should be a corresponding reactive/readonly.
          function wrappedCallback(value: unknown, key: unknown) {
          return callback.call(thisArg, wrap(value), wrap(key), observed)
          }
          return getProto(target).forEach.call(target, wrappedCallback)
          }
          }
          • createIterableMethod 方法 主要是對集合中的迭代進行代理,['keys', 'values', 'entries', Symbol.iterator] 主要是這四個方法。
          const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
          iteratorMethods.forEach(method => {
          mutableInstrumentations[method as string] = createIterableMethod(
          method,
          false,
          false
          )
          readonlyInstrumentations[method as string] = createIterableMethod(
          method,
          true,
          false
          )
          shallowInstrumentations[method as string] = createIterableMethod(
          method,
          true,
          true
          )
          })
          • 可以看到,這個方法也會觸發(fā) TrackOpTypes.ITERATE 類型的 track,同樣也會在遍歷的時候?qū)χ颠M行 wrap,需要主要的是,這個方法主要是 iterator protocol 進行一個 polyfill, 所以需要實現(xiàn)同樣的接口方便外部進行迭代。
          function createIterableMethod(
          method: string | symbol,
          isReadonly: boolean,
          shallow: boolean
          ) {
          return function(this: IterableCollections, ...args: unknown[]) {
          const target = toRaw(this)
          const isMap = target instanceof Map
          const isPair = method === 'entries' || (method === Symbol.iterator && isMap)
          const isKeyOnly = method === 'keys' && isMap
          const innerIterator = getProto(target)[method].apply(target, args)
          const wrap = isReadonly ? toReadonly : shallow ? toShallow : toReactive
          !isReadonly &&
          track(
          target,
          TrackOpTypes.ITERATE,
          isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
          )
          // return a wrapped iterator which returns observed versions of the
          // values emitted from the real iterator
          return {
          // iterator protocol
          next() {
          const { value, done } = innerIterator.next()
          return done
          ? { value, done }
          : {
          value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
          done
          }
          },
          // iterable protocol
          [Symbol.iterator]() {
          return this
          }
          }
          }
          }
          • 總的來說對集合的代理,就是對集合方法的代理,在集合方法的執(zhí)行的時候,進行不同類型的 key 的 track 或者 trigger。

          ref 其實就是 reactive 包了一層,讀取值要要通過 ref.value 進行讀取,同時進行 track ,而設(shè)置值的時候,也會先判斷相對于舊值是否有變化,有變化才進行設(shè)置,以及 trigger。話不多說,下面就進行 ref 的分析。

          • 通過 createRef 創(chuàng)建 ref,如果傳入的 rawValue 本身就是一個 ref 的話,直接返回。

          • 而如果 shallow 為 false, 直接讓 ref.value 等于 value,否則對 rawValue 進行 convert 轉(zhuǎn)化成 reactive。可以看到 __v_isRef 標識 一個對象是否是 ref,讀取 value 觸發(fā) track,設(shè)置 value 而且 newVal 的 toRaw 跟 原先的 rawValue 不一致,則進行設(shè)置,同樣對于非 shallow 也進行 convert。

          export function ref(value?: unknown) {
          return createRef(value)
          }
          const convert = <T extends unknown>(val: T): T =>
          isObject(val) ? reactive(val) : val
          function createRef(rawValue: unknown, shallow = false) {
          if (isRef(rawValue)) {
          return rawValue
          }
          let value = shallow ? rawValue : convert(rawValue)
          const r = {
          __v_isRef: true,
          get value() {
          track(r, TrackOpTypes.GET, 'value')
          return value
          },
          set value(newVal) {
          if (hasChanged(toRaw(newVal), rawValue)) {
          rawValue = newVal
          value = shallow ? newVal : convert(newVal)
          trigger(
          r,
          TriggerOpTypes.SET,
          'value',
          __DEV__ ? { newValue: newVal } : void 0
          )
          }
          }
          }
          return r
          }
          • triggerRef 手動觸發(fā) trigger ,對 shallowRef 可以由調(diào)用者手動觸發(fā)。unref 則是反向操作,取出 ref 中的 value 值。
          export function triggerRef(ref: Ref) {
          trigger(
          ref,
          TriggerOpTypes.SET,
          'value',
          __DEV__ ? { newValue: ref.value } : void 0
          )
          }

          export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
          return isRef(ref) ? (ref.value as any) : ref
          }
          • toRefs 是將一個 reactive 對象或者 readonly 轉(zhuǎn)化成 一個個 refs 對象,這個可以從 toRef 方法可以看出。
          export function toRefs<T extends object>(object: T): ToRefs<T> {
          if (__DEV__ && !isProxy(object)) {
          console.warn(`toRefs() expects a reactive object but received a plain one.`)
          }
          const ret: any = {}
          for (const key in object) {
          ret[key] = toRef(object, key)
          }
          return ret
          }

          export function toRef<T extends object, K extends keyof T>(
          object: T,
          key: K
          ): Ref<T[K]> {
          return {
          __v_isRef: true,
          get value(): any {
          return object[key]
          },
          set value(newVal) {
          object[key] = newVal
          }
          } as any
          }
          • 需要提到 baseHandlers 一點的是,對于非 shallow 模式中,對于 target 不是數(shù)組,會直接拿 ref.value 的值,而不是 ref。
           if (isRef(res)) {
          if (targetIsArray) {
          !isReadonly && track(target, TrackOpTypes.GET, key)
          return res
          } else {
          // ref unwrapping, only for Objects, not for Arrays.
          return res.value
          }
          }

          而 set 中,如果對于 target 是對象,oldValue 是 ref, value 不是 ref,直接把 vlaue 設(shè)置給 oldValue.value

          if (!shallow) {
          value = toRaw(value)
          if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
          oldValue.value = value
          return true
          }
          }
          • 需要注意的是, ref 還支持自定義 ref,就是又調(diào)用者手動去觸發(fā) track 或者 trigger,就是通過工廠模式生成我們的 ref 的 get 和 set
          export type CustomRefFactory<T> = (
          track: () => void,
          trigger: () => void
          ) => {
          get: () => T
          set: (value: T) => void
          }

          export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
          const { get, set } = factory(
          () => track(r, TrackOpTypes.GET, 'value'),
          () => trigger(r, TriggerOpTypes.SET, 'value')
          )
          const r = {
          __v_isRef: true,
          get value() {
          return get()
          },
          set value(v) {
          set(v)
          }
          }
          return r as any
          }
          • 這個用法,我們可以在測試用例找到,
           const custom = customRef((track, trigger) => ({
          get() {
          track()
          return value
          },
          set(newValue: number) {
          value = newValue
          _trigger = trigger
          }
          }))

          computed 就是計算屬性,可能會依賴其他 reactive 的值,同時會延遲和緩存計算值,具體怎么操作。show the code。需要注意的是,computed 不一定有 set 操作,因為可能是只讀 computed。

          • 首先我們會對傳入的 getterOrOptions 進行解析,如果是方法,說明是只讀 computed,否則從 getterOrOptions 解析出 get 和 set 方法。

          • 緊接著,利用 getter 創(chuàng)建 runner effect,需要注意的 effect 的三個參數(shù),第一是 lazy ,表明內(nèi)部創(chuàng)建 effect 之后,不會立即執(zhí)行。第二是 coumputed, 表明 computed 上游依賴改變的時候,會優(yōu)先 trigger runner effect,而 runner 也不會在這時被執(zhí)行的,原因看第三。第三,我們知道,effect 傳入 scheduler 的時候, effect 會 trigger 的時候會調(diào)用 scheduler 而不是直接調(diào)用 effect。而在 computed 中,我們可以看到 trigger(computed, TriggerOpTypes.SET, 'value') 觸發(fā)依賴 computed 的 effect 被重新收集依賴。同時因為 computed 是緩存和延遲計算,所以在依賴 computed effect 重新收集的過程中,runner 會在第一次計算 value,以及重新讓 runner 被收集依賴。這也是為什么要 computed effect 的優(yōu)先級要高的原因,因為讓 依賴的 computed的 effect 重新收集依賴,以及讓 runner 最早進行依賴收集,這樣才能計算出最新的 computed 值。

          export function computed<T>(
          getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
          ) {
          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
          }

          let dirty = true
          let value: T
          let computed: ComputedRef<T>

          const runner = effect(getter, {
          lazy: true,
          // mark effect as computed so that it gets priority during trigger
          computed: true,
          scheduler: () => {
          if (!dirty) {
          dirty = true
          trigger(computed, TriggerOpTypes.SET, 'value')
          }
          }
          })
          computed = {
          __v_isRef: true,
          // expose effect so computed can be stopped
          effect: runner,
          get value() {
          if (dirty) {
          value = runner()
          dirty = false
          }
          track(computed, TrackOpTypes.GET, 'value')
          return value
          },
          set value(newValue: T) {
          setter(newValue)
          }
          } as any
          return computed
          }
          • 從上面可以看出,effect 有可能被多次調(diào)用,像下面中 value.foo++,會導(dǎo)致 effectFn 運行兩次,因為同時被 effectFn 同時被 effectFn 和 c1 依賴了。PS: 下面這個測試用例是自己寫的,不是 Vue 里面的。
          it('should trigger once', () => {
          const value = reactive({ foo: 0 })
          const getter1 = jest.fn(() => value.foo)
          const c1 = computed(getter1)
          const effectFn = jest.fn(() => {
          value.foo
          c1.value
          })
          effect(effectFn)
          expect(effectFn).toBe(1)
          value.foo++
          // 原本以為是 2
          expect(effectFn).toHaveBeenCalledTimes(3)
          })
          • 對于 computed 暴露出來的 effect ,主要為了調(diào)用 effect 里面 stop 方法停止依賴收集。至此,響應(yīng)式模塊分析完畢。

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端算法源碼編程群,每日一刷(工作日),每題瓶子君都會很認真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 87
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  av亚洲产国偷v产偷v自拍牛牛 | 小黄片下载免费视频 | 日韩三级视频网站 | 黄片视频在线看 | 性爱视频免费网站 |