国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

「不容錯(cuò)過(guò)」2.7萬(wàn)字手摸手解讀Vue3.0源碼響應(yīng)式系統(tǒng)

共 33600字,需瀏覽 68分鐘

 ·

2020-11-05 19:12

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

作者:KC

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

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

export function effect(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
  • 其中 option 的參數(shù)如下,都是屬于可選的。

參數(shù) & 含義

  • lazy 是否延遲觸發(fā) effect
  • computed 是否為計(jì)算屬性
  • scheduler 調(diào)度函數(shù)
  • onTrack 追蹤時(shí)觸發(fā)
  • onTrigger 觸發(fā)回調(diào)時(shí)觸發(fā)
  • onStop 停止監(jiān)聽(tīng)時(shí)觸發(fā)
export interface ReactiveEffectOptions {
lazy?: boolean
computed?: boolean
scheduler?: (job: ReactiveEffect) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
}
  • 分析完參數(shù)之后,繼續(xù)我們一開(kāi)始的分析。當(dāng)我們調(diào)用 effect 時(shí),首先判斷傳入的 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(
fn: (...args: any[]) => T,
options: ReactiveEffectOptions
): ReactiveEffect {
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, 唯一標(biāo)識(shí)effect

  • _isEffect 用于標(biāo)識(shí)方法是否是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 方法之后,那么先前沒(méi)有傳入調(diào)用 scheduler 函數(shù)的話(huà),直接調(diào)用原始方法fn,否則直接返回。

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

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,會(huì)導(dǎo)致 num2 改變,觸發(fā)了 spy1, spy1 里面 num1 改變又觸發(fā)了 spy2, spy2 又會(huì)改變 num2,從而觸發(fā)了死循環(huán)。

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

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

const add = (effectsToAdd: Set | 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 類(lèi)型的不同進(jìn)行 effect 的處理。如果是 clear 類(lèi)型,則觸發(fā)這個(gè)對(duì)象所有的 effect。如果 key 是 length , 而且 target 是數(shù)組,則會(huì)觸發(fā) key 為 length 的 effects ,以及 key 大于等于新 length的 effects, 因?yàn)檫@些此時(shí)數(shù)組長(zhǎng)度變化了。
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)
}
})
}
  • 下面則是對(duì)正常的新增、修改、刪除進(jìn)行 effect 的分組, isAddOrDelete 表示新增 或者不是數(shù)組的刪除,這為了對(duì)迭代 key的 effect 進(jìn)行觸發(fā),如果 isAddOrDelete 為 true 或者是 map 對(duì)象的設(shè)值,則觸發(fā) isArray(target) ? 'length' : ITERATE_KEY 的 effect ,如果 isAddOrDelete 為 true 且 對(duì)象為 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))
}
}
  • 最后是運(yùn)行 effect, 像上面所說(shuō)的,computed effects 會(huì)優(yōu)先運(yùn)行,因?yàn)?computed effects 在運(yùn)行過(guò)程中,第一次會(huì)觸發(fā)上游把cumputed effect收集進(jìn)去,再把下游 effect 收集起來(lái)。

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

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, 都會(huì)導(dǎo)致 effect 重新運(yùn)行去收集依賴(lài)。

  • 最后再講一個(gè) stop 方法,當(dāng)我們調(diào)用 stop 方法后,會(huì)清空其他對(duì)象對(duì) effect 的依賴(lài),同時(shí)調(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 的時(shí)候,不會(huì)進(jìn)行依賴(lài)的重新收集,而且沒(méi)有調(diào)度函數(shù),就直接返回原始的 fn 的運(yùn)行結(jié)果,否則直接返回 undefined。
if (!effect.active) {
return options.scheduler ? undefined : fn(...args)
}

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

  • 下面快速看看 vue3 是怎么劫持。首先看看這個(gè)對(duì)象是是不是 __v_isReadonly 只讀的,這個(gè)枚舉在后面進(jìn)行講述,如果是,直接返回,否者調(diào)用 createReactiveObject 進(jìn)行創(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 中,有個(gè)四個(gè)參數(shù),target 就是我們需要傳入的對(duì)象,isReadonly 表示要?jiǎng)?chuàng)建的代理是不是只可讀的,baseHandlers 是對(duì)進(jìn)行基本類(lèi)型的劫持,即 [Object,Array] ,collectionHandlers 是對(duì)集合類(lèi)型的劫持, 即 [Set, Map, WeakMap, WeakSet]。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler
) {
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)是個(gè) proxy ,而且不是要求這個(gè)proxy 是已讀的,但這個(gè) proxy 是個(gè)響應(yīng)式的,則直接返回這個(gè) target。什么意思呢?我們創(chuàng)建的 proxy 有兩種類(lèi)型,一種是響應(yīng)式的,另外一種是只讀的。

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

  • 如果上面都不滿(mǎn)足,則需要檢查一下我們傳進(jìn)去的 target 是否可以進(jìn)行劫持觀察,如果 target 上面掛載了 __v_skip 屬性 為 true 或者 不是我們?cè)僭谏厦嬷v參數(shù)時(shí)候講的六種類(lèi)型,或者 對(duì)象被freeze 了,還是不能進(jìn)行劫持。

const canObserve = (value: Target): boolean => {
return (
!value.__v_skip &&
isObservableType(toRawType(value)) &&
!Object.isFrozen(value)
)
}
  • 如果上面條件滿(mǎn)足,則進(jìn)行劫持,可以看到我們會(huì)根據(jù) target 類(lèi)型的不同進(jìn)行不同的 handler,最后根據(jù)把 observed 掛載到原對(duì)象上,同時(shí)返回 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 用于標(biāo)記對(duì)象不可以進(jìn)行代理,可以用于 創(chuàng)建 component 的時(shí)候,把options 進(jìn)行 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。其實(shí)從字面意思就可以看出他們的區(qū)別了。具體細(xì)節(jié)會(huì)在 collectionHandlers 和 baseHandlers 進(jìn)行講解

baseHandlers 中主要包含四種 handler, mutableHandlers、readonlyHandlers、shallowReactiveHandlers、 shallowReadonlyHandlers。 這里先介紹 mutableHandlers, 因?yàn)槠渌N handler 也算是 mutableHandlers 的變形版本。

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

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

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)
}
  • 接下來(lái)看看 set handler, set 函數(shù)通過(guò) createSetter 工廠方法 進(jìn)行創(chuàng)建,/#PURE/ 是為了 rollup tree shaking 的操作。

  • 對(duì)于非 shallow , 如果原來(lái)的對(duì)象不是數(shù)組, 舊值是 ref,新值不是 ref,則讓新的值 賦值給 ref.value , 讓 ref 去決定 trigger,這里不展開(kāi),ref 會(huì)在ref 章節(jié)展開(kāi)。 如果是 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
}
}
  • 接下來(lái)進(jìn)行設(shè)置,需要注意的是,如果 target 是在原型鏈的值,那么 Reflect.set(target, key, value, receiver) 的設(shè)值值設(shè)置起作用的是 receiver 而不是 target,這也是什么在這種情況下不要觸發(fā) trigger 的原因。

  • 那么在 target === toRaw(receiver) 時(shí),如果原來(lái) 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)
}
}
  • 接下來(lái)說(shuō)說(shuō) get 操作,get 有四種,我們先拿其中一種說(shuō)說(shuō)。
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 中講過(guò)。
 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'] 這三個(gè)方法,則調(diào)用 arrayInstrumentations 進(jìn)行獲取值,
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
  • arrayInstrumentations 中會(huì)觸發(fā)數(shù)組每一項(xiàng)值得 GET 追蹤,因?yàn)?一旦數(shù)組的變了,方法的返回值也會(huì)變,所以需要全部追蹤。對(duì)于 args 參數(shù),如果第一次調(diào)用返回失敗,會(huì)嘗試將 args 進(jìn)行 toRaw 再調(diào)用一次。
const arrayInstrumentations: Record = {}
;['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 是 獲取對(duì)象上面的原型,則直接返回 res 值。

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

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

  • 而如果是 shallow 為 true,說(shuō)明而且不是只讀的,則追蹤 GET 追蹤,這里可以看出,只讀不會(huì)進(jìn)行追蹤。
if (shallow) {
!isReadonly && track(target, TrackOpTypes.GET, key)
return res
}
  • 接下來(lái)都是針對(duì)非 shallow的。 如果返回值是 ref,且 target 是數(shù)組,在非可讀的情況下,進(jìn)行 Get 的 Track 操作,對(duì)于如果 target 是對(duì)象,則直接返回 ref.value,但是不會(huì)在這里觸發(fā) Get 操作,而是由 ref 內(nèi)部進(jìn)行 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
}
}
  • 對(duì)于非只讀,我們還要根據(jù) key 進(jìn)行 Track。而對(duì)于返回值,如果是對(duì)象,我們還要進(jìn)行一層 wrap, 但這層是 lazy 的,也就是只有我們讀取到 key 的時(shí)候,才會(huì)讀下面的 值進(jìn)行 reactive 包裝,這樣可以避免出現(xiàn)循環(huán)依賴(lài)而導(dǎo)致的錯(cuò)誤,因?yàn)檫@樣就算里面有循環(huán)依賴(lài)也不怕,反正是延遲取值,而不會(huì)導(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 ,而對(duì)于 readonlyHandlers,我們可以看出首先不允許任何 set、 deleteProperty 操作,然后對(duì)于 get,我們剛才也知道,不會(huì)進(jìn)行 track 操作。剩下兩個(gè) shallowGet 和 shallowReadonlyGet,就不在講了。
export const readonlyHandlers: ProxyHandler = {
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 主要是對(duì) set、map、weakSet、weakMap 四種類(lèi)型的對(duì)象進(jìn)行劫持。 主要有下面三種類(lèi)型的 handler,當(dāng)然照舊,我們拿其中的 mutableCollectionHandlers 進(jìn)行講解。剩余兩種結(jié)合理解。

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

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

export const readonlyCollectionHandlers: ProxyHandler = {
get: createInstrumentationGetter(true, false)
}
  • mutableCollectionHandlers 主要是對(duì) collection 的方法進(jìn)行劫持,所以主要是對(duì) get 方法進(jìn)行代理,接下來(lái)對(duì) createInstrumentationGetter(false, false) 進(jìn)行研究。

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

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
)
}
}
  • 接下來(lái)看看 mutableInstrumentations ,可以看到 mutableInstrumentations 對(duì)常見(jiàn)集合的增刪改查以及 迭代方法進(jìn)行了代理,我們就順著上面的 key 怎么進(jìn)行攔截的。注意 this: MapTypes 是 ts 上對(duì) this 類(lèi)型進(jìn)行標(biāo)注
const mutableInstrumentations: Record = {
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 ,對(duì) target 進(jìn)行 toRaw, 這個(gè)會(huì)被 createInstrumentationGetter 中的 proxy 攔截返回原始的 target,然后對(duì) key 也進(jìn)行一次 toRaw, 如果兩者不一樣,說(shuō)明 key 也是 reative 的, 對(duì) key 和 rawkey 都進(jìn)行 track ,然后調(diào)用 target 原型上面的 has 方法,如果 key 為 true ,調(diào)用 get 獲取值,同時(shí)對(duì)值進(jìn)行 wrap ,對(duì)于 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 方法差不多,也是對(duì) key 和 rawkey 進(jìn)行 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 方法,同時(shí)觸發(fā) ITERATE 類(lèi)型的 track,而 add 方法添加進(jìn)去之前要判斷原本是否已經(jīng)存在了,如果存在,則不會(huì)觸發(fā) ADD 類(lèi)型的 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 方法是針對(duì) map 類(lèi)型的,從 this 的類(lèi)型我們就可以看出來(lái)了, 同樣這里我們也會(huì)對(duì) key 做兩個(gè)校驗(yàn),第一,是看看現(xiàn)在 map 上面有沒(méi)有存在同名的 key,來(lái)決定是觸發(fā) SET 還是 ADD 的 trigger, 第二,對(duì)于開(kāi)發(fā)環(huán)境,會(huì)進(jì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 就是為了檢查目標(biāo)對(duì)象上面,是不是同時(shí)存在 rawkey 和 key,因?yàn)檫@樣可能會(huì)數(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,但是里面做了一個(gè)防御性的操作,就是如果集合的長(zhǎng)度已經(jīng)為0,則調(diào)用 clear 方法不會(huì)觸發(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 方法的時(shí)候會(huì)觸發(fā) ITERATE 類(lèi)型的 track,需要注意 Size 方法也會(huì)同樣類(lèi)型的 track,畢竟集合整體的變化會(huì)導(dǎo)致整個(gè)兩個(gè)方法的輸出不一樣。順帶提一句,還記得我們的 effect 時(shí)候的 trigger 嗎,對(duì)于 SET | ADD | DELETE 等類(lèi)似的操作,因?yàn)闀?huì)導(dǎo)致集合值得變化,所以也會(huì)觸發(fā) ITERATE_KEY 或則 MAP_KEY_ITERATE_KEY 的 effect 重新收集依賴(lài)。

  • 在調(diào)用原型上的 forEach 進(jìn)行循環(huán)的時(shí)候,會(huì)對(duì) key 和 value 都進(jìn)行一層 wrap,對(duì)于我們來(lái)說(shuō),就是 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 方法 主要是對(duì)集合中的迭代進(jìn)行代理,['keys', 'values', 'entries', Symbol.iterator] 主要是這四個(gè)方法。
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
)
})
  • 可以看到,這個(gè)方法也會(huì)觸發(fā) TrackOpTypes.ITERATE 類(lèi)型的 track,同樣也會(huì)在遍歷的時(shí)候?qū)χ颠M(jìn)行 wrap,需要主要的是,這個(gè)方法主要是 iterator protocol 進(jìn)行一個(gè) polyfill, 所以需要實(shí)現(xiàn)同樣的接口方便外部進(jì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
}
}
}
}
  • 總的來(lái)說(shuō)對(duì)集合的代理,就是對(duì)集合方法的代理,在集合方法的執(zhí)行的時(shí)候,進(jìn)行不同類(lèi)型的 key 的 track 或者 trigger。

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

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

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

export function ref(value?: unknown) {
return createRef(value)
}
const convert = (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 手動(dòng)觸發(fā) trigger ,對(duì) shallowRef 可以由調(diào)用者手動(dòng)觸發(fā)。 unref 則是反向操作,取出 ref 中的 value 值。
export function triggerRef(ref: Ref) {
trigger(
ref,
TriggerOpTypes.SET,
'value',
__DEV__ ? { newValue: ref.value } : void 0
)
}

export function unref(ref: T): T extends Ref ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}
  • toRefs 是將一個(gè) reactive 對(duì)象或者 readonly 轉(zhuǎn)化成 一個(gè)個(gè) refs 對(duì)象,這個(gè)可以從 toRef 方法可以看出。
export function toRefs(object: T): ToRefs {
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(
object: T,
key: K
): Ref {
return {
__v_isRef: true,
get value(): any {
return object[key]
},
set value(newVal) {
object[key] = newVal
}
} as any
}
  • 需要提到 baseHandlers 一點(diǎn)的是,對(duì)于非 shallow 模式中,對(duì)于 target 不是數(shù)組,會(huì)直接拿 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 中,如果對(duì)于 target 是對(duì)象,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)用者手動(dòng)去觸發(fā) track 或者 trigger,就是通過(guò)工廠模式生成我們的 ref 的 get 和 set
export type CustomRefFactory = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}

export function customRef(factory: CustomRefFactory): Ref {
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
}
  • 這個(gè)用法,我們可以在測(cè)試用例找到,
 const custom = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue: number) {
value = newValue
_trigger = trigger
}
}))

computed 就是計(jì)算屬性,可能會(huì)依賴(lài)其他 reactive 的值,同時(shí)會(huì)延遲和緩存計(jì)算值,具體怎么操作。show the code。需要注意的是,computed 不一定有 set 操作,因?yàn)榭赡苁侵蛔x computed。

  • 首先我們會(huì)對(duì)傳入的 getterOrOptions 進(jìn)行解析,如果是方法,說(shuō)明是只讀 computed,否則從 getterOrOptions 解析出 get 和 set 方法。

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

export function computed(
getterOrOptions: ComputedGetter | WritableComputedOptions
) {
let getter: ComputedGetter
let setter: ComputedSetter

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

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++,會(huì)導(dǎo)致 effectFn 運(yùn)行兩次,因?yàn)橥瑫r(shí)被 effectFn 同時(shí)被 effectFn 和 c1 依賴(lài)了。PS: 下面這個(gè)測(cè)試用例是自己寫(xiě)的,不是 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)
})
  • 對(duì)于 computed 暴露出來(lái)的 effect ,主要為了調(diào)用 effect 里面 stop 方法停止依賴(lài)收集。至此,響應(yīng)式模塊分析完畢。

最后


如果你喜歡探討技術(shù),或者對(duì)本文有任何的意見(jiàn)或建議,非常歡迎加魚(yú)頭微信好友一起探討,當(dāng)然,魚(yú)頭也非常希望能跟你一起聊生活,聊愛(ài)好,談天說(shuō)地。魚(yú)頭的微信號(hào)是:krisChans95 也可以?huà)叽a關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。


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

手機(jī)掃一掃分享

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

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 亚洲午夜AV| 亚洲福利女神成人福利| 黄色视频大全在线观看| 蜜桃av一区二区三区| 色猫AV| AV资源网站在线| 不卡日韩| 免费看一区二区三区A片| 女女女女女女BBBBBB手| 久草在线播放| 天天色天天| 国产一级片免费视频| 日韩亚洲视频| 色婷婷五月天| 日韩在线第—页| 91超碰在线免费观看| 人人人操人人| 国产毛片毛片毛片毛片毛片| 色婷婷激情AV| 99热激情在线| 亚洲青草| 日韩V欧美| 欧美亚洲动漫| 色青草影院久久综合| 人人操超碰在线观看| 中文字幕在线亚洲| 亚洲中文字幕第一| 人妻熟女一区二区| 亚洲成人免费视频| 久久AV电影| 正在播放JUQ-878木下凛凛子 | 91丨豆花丨成人熟女| 亚洲日韩欧美国产| 北条麻妃黄色视频| 天堂素人| 极品一线天小嫩嫩真紧| 另类老妇奶性生BBwBB| 加勒比无码视频| 強姧伦一区二区三区在线播放 | 日本在线一级片| 亚洲aⅴ| 操逼欧美| 男女AV网站| 午夜看黄| 日韩黄色电影在线| 无码a片| 国产乱码精品一区二区三区的特点| 日本黄色电影在线| 99re欧美激情| 国产91精品久久久天天| 婷婷亚洲精| 青娱乐91视频| 精品无码一区二区三区四区久久久软件| 久草视频福利在线| 午夜小电影| 国产一级a一片成人AV| 大香蕉伊人久久| 大香蕉98| 久久精品| 日韩三级av| 中文字幕高清免费看| 久久另类TS人妖一区二区| 92午夜福利天堂视频2019| 北条麻纪无码视频| 日本黄色视频官网| 九九热在线精品| 国产免费高清无码| 怡春院在线| 久久福利电影| h网站在线观看| 青青草黄色视频| 波多野结衣黄色| 国产欧美黄片| 欧一美一婬一伦一区二区三区黑人| 干欧美| 91亚洲国产成人精品一区二区三 | 97人人干人人| 51妺嘿嘿在线电影免费观看| 无码人妻少妇| 三级片网站在线观看| 12—13女人毛片毛片| 无码人妻熟妇| 亚洲无码性爱视频| 99九九视频| 色色播| 丁香五月婷婷久久| 日韩大香蕉网| 亚洲视频黄色| 午夜成人鲁丝片午夜精品| 91成人无码视频| 日逼中文字幕| 国产天堂网| 欧美一级黃色A片免费看小优视频| 亚洲成av人无码| 波多野结衣av在线播放| 精品国产AⅤ麻豆| 婷婷五月天黄色| 日韩高清无码电影| 丰满欧美熟妇免费视频| 久久精品视频免费| 久久国产乱子伦精品免费女,网站| 国产一级免费视频| 高清无码免费视频| 中文字幕在线观看福利视频| 五月色综合网| 牛牛精品一区二区AV| 777国产盗摄偷窥精品0000 | 91人妻无码精品一区二区三区 | 久久久久久久毛片| 欧美激情xxx| 国产又粗又大又黄视频| 中文字幕第27页| 草草影院第一页YYCCC| 九九色热| 亚洲无码一卡二卡| 一级成人毛片| 热99视频| 99爱在线观看| 四虎国产| 久草网站| 日本在线一级片| 啊哈嗯| 无码毛片在线观看| 波多野结衣在线无码视频| 嫩BBB搡BBBB搡BBBB-百度| 国产99页| 男人的天堂在线播放| 亚洲精品中文字幕在线观看| 91久久| 一二三四在线视频| 午夜激情在线观看| 就去se超碰| 日韩免费在线观看一区入口| 国产欧美日韩综合精品| 久久久久一区二区三区| 亚洲激色| 成人大片在线观看| 国产免费a| 亚洲免费在线播放| 91人妻人人澡人人爽| 色色99| 日韩黄色A级片| 日本一区二区三区四区在线观看| 97久久超碰| 麻豆精品无码| 国产成人视频免费| 亚洲人妻无码在线| 色老板在线观看永久免费视频| 精品日韩| 久久成人18免费网站波多野结衣 | 亚洲色逼| 国产日逼视频| 国产精品系列视频| 最新亚洲无码在线观看| 日本黄色电影在线观看| 中文字幕国产精品| 无码精品一区二区| 你懂的视频| www.天天射视频| 久久你懂的| 仓井空一区二区三区| 亚洲色欲av| 拍真实国产伦偷精品| 欧美成人一级A片| v天堂在线| 丝瓜视频污APP| 一级A片黃色A片| 伊人大香蕉精品| 亚洲精品成人无码AV在线| 亚洲欧洲精品在线| 亚洲欧美视频在线观看| 欧美综合亚洲图片综合区| 亚洲天堂在线观看视频网站| 丰滿人妻一区二区三| 日韩无码视频网| 亚洲夜夜操| 青青草国产| 精品AV无码一区二区三区| 中文字幕日韩乱伦| 色婷婷在线综合| 色噜噜AV| 欧洲一区二区| 欧美日本激情| 日韩无码你懂的| 老太色HD色老太HD-百度| JLZZJLZZ亚洲女人| 亚洲高清在线播放| 女BBBBBB女BBB| 亚洲视频A| 大炕上公让我高潮了六次| 在线无码一区二区三区| 青青草免费公开视频| 丝袜人妻被操视频| 安微妇搡BBBB搡BBBB日| 亚洲欧美久久久| 人妻av中文字幕| 陈冠希和张柏芝mv| 狠狠躁夜夜躁人人爽人妻| 伊人偷拍视频| 97色色婷婷| 中文在线高清字幕| 天天操天天看| 黄片久久| 秋霞久久日| 国产夫妻自拍av| 国产女人18毛片水真多成人如厕| 久久婷婷六月综合| 国产黄色三级片| 一区二区Av| 91成人视频免费观看| 精品人妻无码一区二区三区四川人 | 精品国产AV鲁一鲁一区| 色五月婷婷激情| 91成人在线免费视频| 无码人妻一区二区三区| 俺来了俺去也| 激情视频在线播放| 人人操人人摸人人看| 黄色资源在线观看| 91大神免费在线观看| 日韩精品观看| 91九色国产| 狠狠操综合| 香蕉操逼小视频| 一起操在线视频| 成人午夜在线视频| 国精产品一区一区三区| 91麻豆福利在线| 久久av一区二区三区观看| 天天摸天天看| 免费人妻视频| 青青娱乐亚洲无| 牛牛在线精品视频| 午夜福利10000| 天天操天天看| 精品国产AV色一区二区深夜久久| 欧美精品亚洲| 99在线播放| 龙泽美曦土豪| 免费电影日本黄色| 伊人狠狠蜜桃亚洲综合| 999精品视频| 国精产品九九国精产品| 亚洲系列中文字幕| 免费无码国产在线观看快色| 亚洲高清福利| 国产免费福利| 无码A∨| 综合天堂网| 日韩超清无码| 精品视频免费| 免费黄色小视频| 国产高清无码免费在线观看| 黄色大片免费在线观看| AV资源在线| 牛牛无码| AV性爱在线| 久久久久久久久久久高清毛片一级| 欧美在线日韩在线| 中文字幕成人无码| 色婷婷丁香五月天| 亚洲无码一区二区在线| 午夜精品人妻无码| 91人人爽| 欧美日韩亚洲另类| 免费在线观看黄视频| 大香蕉精品在线| 国产一精品一aⅴ一免费| 国产AV小电影| 黄片网站免费| 黄片网站入口| 天天操夜操| 国内精品一区二区| 91无码人妻东京热精品一区| 国产精品夜夜爽7777777| 懂色Av| 少婦揉BBBB揉BBBB揉| 人妻丰满熟妇av无码区| 日本三级片在线动| 91精品视频网| 欧美日韩成人在线视频| 国产欧美日韩三级| 中文字幕无码网站| 欧美日韩精品在线观看| 青草在线视频| 北条麻妃无码一区二区| 在线无码| 人人艹在线| 国产1区2区3区| 肏逼在线观看| 少妇搡BBBB搡BBB搡造水多/| 久久精品一区二区三区四区| 大陆搡BBBBB搡BBBBBB| 国产伦精一品二品三品app| 日韩中文字幕在线视频| 国产在线一区二区三区四区| 豆花视频成人精品视频| 成人免费福利| 午夜成人精品| 波多野结衣性爱视频| 日韩第一色| 91探花视频在线观看| 国产91一区在线精品| 国产黄色视频免费在线观看| 欧美日韩精品在线观看| 日本一级婬片A片免费播放一| 免费看成人A片无码照片88hⅤ | 亚洲乱码精品久久久久..| 日韩经典视频在线播放| 粉嫩av在线| 成人欧美一区二区三区黑人免费| 最新亚洲中文字幕| 色婷婷免费视频| 国产成人精品a区在线观看| 无码人妻A片一区二区青苹果| 黄色网页在线观看| 91美女在线视频| 无码精品人妻一区二区三刘亦菲| 熟女视频一区二区| 躁BBB躁BBB躁BBBBB乃| 亚洲午夜久久久之蝌蚪窝| 午夜天堂在线观看| 欧美黄色网| 欧洲性爱视频在线观看| 成人国产综合| 97欧美日韩| 亚洲AV成人精品一区二区三区| 狠狠大香蕉| 青娱乐亚洲精品| 婷婷色综合视频二区| 丁香五月中文| 91久久国产综合久久| 国产精品一级a毛视频| 日本免费中文字幕| 91人人妻人人做人人爽| 人人搞人人摸| 婷婷五月18永久免费视频| 小泬BBBBBB免费看| 中文字幕乱码视频32| 日韩视频――中文字幕| 四川少妇BBB凸凸凸BBB安慰我 | 蜜臀久久99精品久久久| 自拍偷拍第一页| 日韩一区二区三区无码电影| 欧美不卡一区| 午夜专区| 米奇色色| 九九精品免费视频| 日韩性爱网| 国产精品自拍在线观看| 操逼一区二区| 亚洲毛片亚洲毛片亚洲毛片| 亚洲精品国产AV| 一级真人毛片| 日本黄色三级视频| 日韩欧美高清无码| 国产AV无码成人精品毛片| 超碰1999| 国产三级片网址| 99国产综合| 大香蕉中文在线| 国产成人自拍偷拍视频| 欧美大香蕉视频| 黄色无码在线观看| 天天操夜夜骑| 日韩免费a| 久久国产精品视频| 日韩不卡AV| 天堂网址激情网址| 亚洲.欧美.丝袜.中文.综合| 全部免费黄色视频| 伊人春色av| 黄色免费高清视频| 国产成人视频免费观看| 中文字幕亚洲中文字幕| 欧美AAA视频| 婷婷五月伊人| 亚洲一级黄色| 欧美性天天| 国产人人干| 波多野结衣大战黑人| 国产三级在线| 亚洲一区三区| 成人A片在线播放| 欧美黄色大片| 亚洲无码免费在线视频| 奇米av| 超碰1999| 久久蜜| 伊人在线视频观看| 一本一道久久a久久精品综合| 婷婷狠狠干| 牛牛精品一区二区AV| av大全在线观看| 狠狠干B| 成人黄片免费看| 国产免费一级特黄A片| 国产精品视频| 操综合网| 国产一视频| 中文字幕-区二区三区四区视频中国| 日韩高清无码免费观看| 婷婷久久五月| 一级a看片在线观看| 日本伊人在线综合视频| 大陆一级片| 青青免费视频| 99人妻人人爽人人添人人精品| 四川少BBB搡BBB爽爽爽| 人人草人人摸| 91丨露脸丨熟女抽搐| 黄色一级生活片| 岛国AV免费看| 丝袜久久| 91av在线播放| 日本天天操| 亚洲AV无码精品岛国| 国产精品成人免费精品自在线观看| 欧美少妇做爱| 亚洲中文字幕av| 日韩天天| 日韩AV在线天堂| 欧美三级视频在线观看| 东京热黄色电影| A区性愛社区| 欧美在线小视频| 中国12一13毛片| 免费亚洲婷婷| 日韩欧美手机在线| 国产激情内射| 无码AV网站| 大香蕉黄色片| 在线草| 中文字幕免费高清在线观看| 91插插插插| 色婷婷视频| 91免费看片| AV成人无码| 91香蕉视频18| 3d动漫精品H区XXXXX区| 天天日综合网| 大肉大捧一进一出两腿| 少妇激情av| 五月天丁香成人| 五月综合久久| 7799精品视频天天看| 国产无遮挡又黄又爽在线观看| 久久久777| 日韩一级毛| 中国免费视频高清观看| 日本十八禁网站| 嫩草嫩草69| 国产操老女人| 欧美在线色图| 日韩高清无码观看| 狠狠躁夜夜躁人人爽人妻| 无码狠狠躁久久久久久久91| 国内超碰| 欧美三级长视频| 欧美特黄AAAAAAAAA片| 无码熟妇人妻无码AV在线天堂| 欧美三级在线| 99久在线精品99re8热| 自拍三区| 色婷婷黄色| 欧美日韩中文字幕在线| 日韩免费a| 微拍福利一区二区| 精品国产国产没封| 欧美亚洲动漫| 国产日韩一区二区三免费高清| 91性爱视频| 激情国产av| 18禁一区二区| 亚洲国产精品欧美久久| 婷婷精品| 一级Aa视频免费看| 亚洲午夜激情| 99色逼| 国产2区| 欧美激情区| 欧美色图狠狠操| 91在线网站| 欧美老熟女18| 日本国产在线视频| 丰满人妻一区二区免费看| 日本黄色一级视频| 中文字幕在线播放视频| 嫩BBB槡BBBB槡BBBB| 91在线无码精品秘国产三年 | 国产一级在线| 91国产视频在线观看| 欧美成人综合一区| 亚洲无码精品视频| 亚洲精品成a人在线观看| 日本三级片网址| 日本中文字幕乱伦| 爽好紧别夹喷水无码| 黄色网址在线免费观看| 另类老妇奶性BBWBBwBBw| 自拍偷拍一区二区三区| 一本大道东京热av无码| 狠狠狠狠狠狠狠| 插插插综合| 亚洲AAA电影| 亚洲精品秘一区二区三区蜜桃久| aaa久久| 国产乱论视频| 高清毛片AAAAAAAAA片| 亚洲天堂久久| 97欧美| 丁香天堂| AV无码人妻| 中文字幕av久久爽一区| 成人小视频在线观看| 大香蕉1024| AV牛牛| 日本成片网| 51妺妺嘿嘿午夜成人A片| 3344在线观看免费下载视频| 91看片看婬黄大片Videos | 国产在线观看av| 婷婷俺也去| 尻屄视频网站| 91人妻人人澡人人爽人人精吕| 亚洲一级黄色| 欧美又大又粗| 呦小性Free小U女HD| AV电影天堂网| 日本高清一区二区高清免费视频| 国产三级自拍视频| 亚洲精品中文字幕在线观看| gogogo日本免费观看高清电视剧的注意 | 天堂亚洲AV无码精品成人| 国产资源av| 久在线视频| 欧美性爱18| 久久w| 丰满人妻一区二区三区视频在线不卡| 免费看黄色视频| 日本特黄AA片免费视频| 欧美熟妇一区二区三区| 成人性爱视频免费在线观看| 偷拍视频网站北条麻妃| 激情五月综合网| 无码不卡在线观看| 牛牛精品一区| 偷拍一区二区三区| 日韩插插| 久久久国产视频| 黄色高清无码| 风流少妇一区二区三区91| 波多野结衣一级| 久久久WWW成人免费无遮挡大片| 996视频| 嫩BBB槡BBBB槡BBBB撒尿-百度| 丁香花小说完整视频免费观看| 人人操人人搞| 午夜福利电影AV| av天堂小说网| 俩小伙3p老熟女露脸| 97国产| 色99999| 北京熟妇槡BBBB槡BBBB| 中文字幕一二三区| 精品777| 亚洲日韩在线视频观看| 色综合网址| 久久精品一区| 成人性生活免费视频| 亚洲免费黄色电影| 性天堂| 大香蕉黄色片| 黄色生活片| 日韩中文字幕区| 青青三级片| 日本精品人妻无码77777| 无码A区| 69精品无码成人久久久久久| 亚洲精品女人久久久| 久久人妻精品| 国产亚洲精品久久久波多野结衣| 亚洲一级二级片| 免费黄片网站| 欧美日韩小视频| 嫩小槡BBBB槡BBBB槡漫画| 51国产黑料吃瓜在线入口| 好吊顶亚洲AV大香蕉色色| 中国精品77777777| 日韩av成人| 无码人妻精品一区二区50| 黄网国产手机在线观看| 毛片网站在线观看| 亚洲骚逼| 六月婷婷久久| 亚洲a∨| www.五月丁香| 国产免费福利| 7x7x7x人成免费观学生视频 | 九九热精品视频99| 欧美三级长视频| 欧美中文字幕在线| 一区毛片| 婷婷狠狠| 亚洲一区AV| 黃色级A片一級片| 超碰福利在线| 一本色道无码道| 天堂网亚洲| 欧美中文字幕视频| 久久久久亚洲AV无码专区| 怡红院成人在线| 欧美裸体视频| 国产精品系列视频| 久久中文字幕综合| 人人妻人人澡| 亚洲成人免费视频| 无码精品ThePorn| 欧美黄视频| av一级片| 99热99精品| 99热最新在线| 日韩特级毛片| 无码不卡在线| 中文无码日本一级A片人| 99黄片| 超碰成人AV| 国产黄色视频免费在线观看| 国产精品av在线播放| 在线视频福利导航| 在线播放91灌醉迷J高跟美女 | 山东乱子伦视频国产| 黑人AV在线播放| 国产成人AA| 亚洲国产精品成人网站| 2025中文在线观看最好看的电影| 午夜操p| 曰本中文字幕在线视频| 91成人片| 精品久| 日韩在线不卡| 日韩18禁| 毛片毛片毛片| 波多野结衣无码高清| 水蜜桃一区二区| 中文字幕在线视频观看| 亚洲无码视频观看| 黄色片免费在线观看| 爱爱动态图| av天天干| 黄色三级视频在线观看| 丁香激情五月天| 97超碰在线播放| 国产精品9| 91人妻成人精品一区二区| 亚洲高清无码视频在线观看| 日韩精品第一页| 免费黄片视频| 日韩欧美激情| 日韩黄色毛片| 日韩精品三区| 精品无码国产一区二区三区51安| 国产一级在线观看| h片在线免费观看| 成人大香蕉| 国产美女精品久久AV爽| 就去se超碰| 操你啦无码日韩| 美女大吊,网站视频| 欧美日韩在线观看一区| 日本高清视频www| 免费观看高清无码视频| 极品在线视频| 蜜桃传媒在线| 一级aa视频| 欧美日韩美女| 爱爱视频天天操| 丁香午夜| 久久大鸡| 亚卅无码| 91丝袜一区二区三区| 囯产精品一区二区三区线一牛影视1| 日韩成人精品在线| 国产精品成人在线| 国产四区| 北条麻妃无码| 免费看黃色AAAAAA片| 国产高清无码免费| 日韩视频中文字幕| 91国黄色毛片在线观看| 美女黄色视频永费在线观看网站 | 日韩国无码| 三级片小说| 今天成全在线观看高清| 91亚洲视频在线观看| 亚洲视频第一页| 久久三级| 日韩无码中文字幕| 久久精品免费看| 你懂的在线视频观看| 亚洲免费av在线| 精品AAA| 青青草成人AV| 国产精品V亚洲精品V日韩精品| 中国免费XXXX18| 国产无码中文| 日本高潮视频| 久久久久久久性爱| 婷婷日韩中文字幕| 一级日韩| 2025av中文字幕| 性爱视频亚洲| 亚洲在线无码播放| 永久中文字幕| 久久精品五月天| 国产高清久久| 欧美丰满美乳XXⅩ高潮www| 一本大道DVD中文字幕| 狠狠躁日日躁夜夜躁A片无码 | 黄色片免费视频网站| 中文原创麻豆传媒md0052| 午夜国产在线视频| 九一无码| 日韩少妇无码视频| 中文字幕乱码中文字幕| 国内老熟妇对白HDXXXX| 黄片久久| 大香蕉电影网| 深爱激情综合| 久热中文在线观看精品视频| 精品热99| 日本黄色视频在线免费观看 | 一二三四在线视频| 国产成人A片| 浪潮在线观看完整版| 久久911| 青青草视频免费观看| 日韩AV电影网站| 日韩精品免费无码视频| 18禁网站免费| 久久久网| 久久成人免费视频| 日韩三级片AV| 草久av| 大香蕉尹人在看| 一本色道无码道| 亚洲一级黄色视频| 国产在线观看国产精品产拍| 日韩加勒比| 激情五月毛片| 亚洲国产高清国产精品| 多啪啪免费视频| 午夜成人在线视频| 91视频内射| 玉米地一级婬片A片| 欧美丝袜脚交xxxxBH| 国产人妻精品一区二区三区不卡| 欧美一区二区三区系列电影 | 操操综合| 午夜成人在线视频| 91色在线观看| 黄网站免费看| 综合色国产精品欧美在线观看| 特黄A级毛片| 国产精品一级a毛视频| 肏屄视频在线播放| 亚洲av免费在线| 91人人干| 久久精品一区| 97福利导航| 波多野结衣操逼| 亚洲日韩精品在线观看| 777Av| 日韩免费av| 亚洲三级网站在线观看| 国产精品毛片VA一区二区三区 | 特级西西人体444WWw高清大胆| 亚洲美女视频在线| 俄罗斯老熟妇与子伦| 91无码人妻一区二区| 亚洲日韩在线观看视频| 大香蕉一区二区| 六月婷婷五月天| 亚洲中字幕| 永久免费黄色视频| 久久艹大香蕉| 波多野结衣无码视频| 三级片网站国产| 无码波多野结衣| 77久久| 蜜桃精品在线观看| 国产小视频在线| 麻妃无码| 六月婷婷五月| 日韩欧美视频在线| 先锋资源av| 久久Av电影| 少妇的屄| 高清成人无码| 超碰在线观看91| 日本伊人在线综合视频| 久久久久久久久免费视频| 国内精品久久久久久久久久变脸 | 天堂中文在线资源| 国产男女av| 久久无码专区| 欧美男人的天堂| 国产激情网站| 欧美午夜片| 蜜桃人妻无码AV天堂二区| 麻豆日韩| 青青草操逼视频| 色综合视频| 欧美v在线观看| 无码专区在线观看| 中文字幕亚洲在线观看| 亚洲美女喷水视频| 蜜桃精品一区二区| 免费做a爰片77777| 性无码一区二区三区| 麻豆成人片| 中文字幕+乱码+中文乱码91在线观看 | 婷婷五月天青草| 丁香五月激情啪啪| 女生操逼网站| 国产一级a爱做片免费☆观看| 长泽梓黑人初解禁BDD07| 欧美v在线观看| 久久做爱视频| 91中文字幕在线| 国产激情电影| 国内精品久久久久久久久98| 中文字幕不卡在线| 国产午夜视频| 精品网站| 日韩一级片在线播放| 亚洲永久免费| 看国产毛片| 日韩色逼| 欧美日本国产| 性爱一区| av无码av天天av天天爽| 国产无码操逼| 久久性| 91福利资源| 97精品欧美91久久久久久久| AV资源站| 久久精品99视频| 影音先锋成人在线视频| 热99视频| 日无码| 性爱av在线观看| 天天色天天色天天色| 偷拍一区| 国产AV一卡| 天堂网AV在线| 高清无码在线观看视频| 五月婷婷狠狠爱| 无码精品一区二区在线| 欧美日韩91| 日狠狠| NP玩烂了公用爽灌满视频播放 | 成人黄色网址| 国产免费www| 日韩一级电影在线观看| 老师机性爱视频在线播放| 欧美日韩网| 欧美婷婷五月天| 欧美footjob高跟脚交| 黄色一级大片在线免费看产| 五月天婷婷基地| 正在播放李彩斐被洋老外| 亚洲AV无码乱码国产精品黑人| 无码狠狠躁久久久久久久91| 国产曰韩欧美综合另类在线| 免费黄色a片| 久草免费电影| 五月天激情午夜福利| 免费在线观看A| 成人特级毛片全部免费播放| 欧美色视频网| 日韩欧美一区在线| 亚洲三级在线播放| 日韩AV无码免费| 亚洲成人精品一区二区| 成人A片免费| 亚洲欧洲自拍| 九九视频免费观看| 久久一级片| 亚洲天堂三级片| 大香蕉av在线观看| 99美女精品视频| 色综合大香蕉| 夜夜操免费视频| 97香蕉久久夜色精品国产| 欧美国产日韩在线观看| 久久三级| 国产又爽又黄视频在线看| 欧美97| 粉嫩一区二区三区四区|