精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

徹底搞懂Vue響應(yīng)式數(shù)據(jù)、依賴收集更新、Watch/Computed原理

開發(fā) 前端
在合并配置mergeOptions()中,會調(diào)用normalizeProps()對props的數(shù)據(jù)進(jìn)行整理,最終確保initPros調(diào)用時props已經(jīng)是一個對象,因此不需要Observer判斷是否是數(shù)組,直接對key進(jìn)行defineReactive即可。

響應(yīng)式原理初始化

響應(yīng)式數(shù)據(jù)設(shè)置代理

  • 訪問props的item對應(yīng)的key時,使用this.[key]會自動代理到vm._props.[key]
  • 訪問data的item對應(yīng)的key1時,使用this.[key1]會自動代理到vm._data.[key1]
function initProps(vm: Component, propsOptions: Object) {
    for (const key in propsOptions) {
        if (!(key in vm)) {
            proxy(vm, `_props`, key)
        }
    }
}
function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {};
    const keys = Object.keys(data)
    const props = vm.$options.props
    const methods = vm.$options.methods
    let i = keys.length
    while (i--) {
        const key = keys[i]
        // 監(jiān)測props是否已經(jīng)有這個key了,有的話彈出警告
        proxy(vm, `_data`, key)
    }
}
export function proxy(target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

Vue.props響應(yīng)式數(shù)據(jù)設(shè)置

在合并配置mergeOptions()中,會調(diào)用normalizeProps()對props的數(shù)據(jù)進(jìn)行整理,最終確保initPros調(diào)用時props已經(jīng)是一個對象,因此不需要Observer判斷是否是數(shù)組,直接對key進(jìn)行defineReactive即可

function initProps(vm: Component, propsOptions: Object) {
    const propsData = vm.$options.propsData || {}
    const props = vm._props = {}
    const keys = vm.$options._propKeys = []

    for (const key in propsOptions) {
        keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        defineReactive(props, key, value)
    }
}

Vue.data響應(yīng)式數(shù)據(jù)設(shè)置

  • 為data建立一個Observer,主要功能是根據(jù)value類型判斷,是數(shù)組則遞歸調(diào)用observe,為每一個item都創(chuàng)建一個Observer對象,如果是對象,則遍歷key,為每一個key都創(chuàng)建響應(yīng)式監(jiān)聽
function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
    // observe data
    observe(data, true /* asRootData */)
}

export function observe(value: any, asRootData: ?boolean): Observer | void {
    if (!isObject(value) || value instanceof VNode) {
        return
    }
    // ... 判斷數(shù)據(jù)value是否已經(jīng)設(shè)置響應(yīng)式過
    let ob = new Observer(value)
    return ob
}
export class Observer {
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data

    constructor(value: any) {
        this.value = value
        this.dep = new Dep()
        if (Array.isArray(value)) {
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    walk(obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i])
        }
    }

    observeArray(items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[i])
        }
    }
}

Object.defineProperty響應(yīng)式基礎(chǔ)方法

  • get:返回對應(yīng)key的數(shù)據(jù) + 依賴收集
  • set:設(shè)置對應(yīng)key的數(shù)據(jù)+派發(fā)更新
export function defineReactive(obj: Object, key: string, val: any, ...args) {
    const dep = new Dep()
    let childOb = !shallow && observe(val) // 如果val也是object

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    // key對應(yīng)的val是Object,當(dāng)val里面的key發(fā)生改變時
                    // 即obj[key][key1]=xxx
                    // 也會通知目前obj[key]收集的Watcher的更新
                    childOb.dep.depend()
                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
        }
    })
}

Dep響應(yīng)式依賴管理類

  • 每一個key都有一個Dep管理類
  • Dep具備addSub,即關(guān)聯(lián)Watcher(渲染W(wǎng)atcher或者其它)的能力
  • Dep具備depend(),被Watcher顯式關(guān)聯(lián),可以被Watcher觸發(fā)dep.notify()通知它關(guān)聯(lián)Watcher更新的能力
Dep.target = null
const targetStack = []
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Watcher響應(yīng)式依賴收集和派發(fā)更新執(zhí)行類

  • get()方法進(jìn)行pushTarget(this),觸發(fā)對應(yīng)的getter回調(diào),開始收集,然后popTarget(this),停止收集,最后觸發(fā)cleanupDeps()進(jìn)行依賴的更新
  • update()將更新內(nèi)容壓入隊列中,然后根據(jù)順序調(diào)用Watcher.run(),也就是回調(diào)constructor()傳進(jìn)來的this.cb方法
export default class Watcher {
    constructor(...args) {
        this.vm = vm
        if (isRenderWatcher) {
            vm._watcher = this
        }
        vm._watchers.push(this)
        this.cb = cb; // 觸發(fā)更新時調(diào)用的方法
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.value = this.lazy
            ? undefined
            : this.get()
    }

    get() {
        pushTarget(this)
        let value
        const vm = this.vm
        value = this.getter.call(vm, vm)
        if (this.deep) {
            traverse(value)
        }
        popTarget()
        this.cleanupDeps()
        return value
    }

    addDep(dep: Dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id)
            this.newDeps.push(dep)
            if (!this.depIds.has(id)) {
                dep.addSub(this)
            }
        }
    }

    cleanupDeps() {
        let i = this.deps.length
        while (i--) {
            const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) {
                dep.removeSub(this)
            }
        }
        let tmp = this.depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        this.newDepIds.clear()
        tmp = this.deps
        this.deps = this.newDeps
        this.newDeps = tmp
        this.newDeps.length = 0
    }


    update() {
        if (this.lazy) {
            this.dirty = true
        } else if (this.sync) {
            this.run()
        } else {
            queueWatcher(this)
        }
    }

   
    run() {
        if (this.active) {
            const value = this.get()
            if (value !== this.value || isObject(value) || this.deep) {
                const oldValue = this.value
                this.value = value
                if (this.user) {
                    const info = `callback for watcher "${this.expression}"`
                    invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
                } else {
                    this.cb.call(this.vm, value, oldValue)
                }
            }
        }
    }

    depend() {
        let i = this.deps.length
        while (i--) {
            this.deps[i].depend()
        }
    }
}

Object數(shù)據(jù)類型響應(yīng)式

最外一層key的響應(yīng)式設(shè)置

使用observe()對每一個Object的key都進(jìn)行Object.defineProperty()劫持

function observe(value, asRootData) {
    ob = new Observer(value);
    return ob
}

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    this.walk(value);
};
walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i])
  }
}
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key]
    }
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            dep.notify()
        }
    })
}

深度key的響應(yīng)式設(shè)置

export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {
    const dep = new Dep()
    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    childOb.dep.depend()
                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
        }
    })
}
  • 由上面對observe()方法的分析,它會遍歷Object的每一個key,進(jìn)行Object.defineProperty聲明
  • 對于Object每一個key對應(yīng)的value,如果childOb = !shallow && observe(val)不為空,那么它會遍歷value對應(yīng)的每一個key,如果value[key]也是一個Object,那么會再次走到childOb = !shallow && observe(val),直到所有Object都為響應(yīng)式數(shù)據(jù)為止
  • 對于obj[key]來說,會調(diào)用dep.depend(),如果obj[key]本身也是一個對象,即childOb不為空,那么它就會調(diào)用childOb.dep.depend(),因此當(dāng)obj[key][key1]=xx時,也會觸發(fā)dep.depend()收集的Watcher發(fā)生更新,例如
data: {
  parent: {
    children: {test: "111"}
  }
}


<div>{{parent.children}}</div>

由上面的分析可以知道,當(dāng)this.parent.children.test發(fā)生變化時,會觸發(fā)this.parent.children收集的渲染W(wǎng)atcher發(fā)生變化,從而觸發(fā)界面重新渲染

額外添加key

由于Object.defineProperty()的限制,無法實現(xiàn)對Object新增key的響應(yīng)式監(jiān)聽,因此當(dāng)我們想要為Object設(shè)置新的key的時候,需要調(diào)用Vue.set方法

export function set(target: Array<any> | Object, key: any, val: any): any {
    if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val;
    }
    const ob = (target: any).__ob__;
    if (!ob) {
        target[key] = val;
        return val;
    }
    defineReactive(ob.value, key, val);
    ob.dep.notify();
    return val;
}

Vue.set()的流程可以總結(jié)為:

  • 為Object增加對應(yīng)的key和value數(shù)據(jù)
  • 將新增的key加入響應(yīng)式監(jiān)聽中,如果key對應(yīng)的value也是Object,按照上面深度key的監(jiān)聽設(shè)置分析,會遞歸調(diào)用observe進(jìn)行深度key的響應(yīng)式設(shè)置
  • 手動觸發(fā)Object收集的Watcher的刷新操作

本質(zhì)上,上面的三步流程除了第二步有略微差別之外,其它部分跟defineReactive中的set()方法流程一致

刪除key

刪除key也無法觸發(fā)響應(yīng)式的變化,需要手動調(diào)用Vue.del()方法:

  • 刪除Object指定的key
  • 手動觸發(fā)Object收集的Watcher的刷新操作
function del(target: Array<any> | Object, key: any) {
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1)
        return
    }
    const ob = (target: any).__ob__
    if (!hasOwn(target, key)) {
        return
    }
    delete target[key]
    if (!ob) {
        return
    }
    ob.dep.notify()
}

Array數(shù)據(jù)類型響應(yīng)式

前置說明

根據(jù)官方文檔[1]說明,Vue 不能檢測以下數(shù)組的變動

  • 當(dāng)你利用索引直接設(shè)置一個數(shù)組項時,例如:vm.items[indexOfItem] = newValue
  • 當(dāng)你修改數(shù)組的長度時,例如:vm.items.length = newLength

舉個例子:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是響應(yīng)性的
vm.items.length = 2 // 不是響應(yīng)性的

為了解決第一類問題,以下兩種方式都可以實現(xiàn)和 vm.items[indexOfItem] = newValue 相同的效果,同時也將在響應(yīng)式系統(tǒng)內(nèi)觸發(fā)狀態(tài)更新

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

為了解決第二類問題,你可以使用 splice:

vm.items.splice(newLength)

對Array[index]數(shù)據(jù)的響應(yīng)式監(jiān)聽

如果item=Array[index]是Object數(shù)據(jù),使用observe()對Array的每一個item都進(jìn)行響應(yīng)式的聲明

function observe(value, asRootData) {
    ob = new Observer(value);
    return ob
}

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
        if (hasProto) {
            protoAugment(value, arrayMethods)
        } else {
            copyAugment(value, arrayMethods, arrayKeys)
        }
        this.observeArray(value)
    }
};

observeArray(items: Array < any >) {
    for (let i = 0, l = items.length; i < l; i++) {
        observe(items[i])
    }
}

Vue.set更新Array-item

從下面代碼可以看出,Vue.set()更新數(shù)組的item本質(zhì)上也是調(diào)用Array.splice()方法

export function set(target: Array<any> | Object, key: any, val: any): any {
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        return val
    }
}

Array.splice更新Array-item

從上面的分析可以知道,一開始會觸發(fā)new Observer(value)的初始化從下面代碼可以知道,大部分瀏覽器會觸發(fā)protoAugment()方法,也就是改變Array.__proto__

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
        if (hasProto) {
            protoAugment(value, arrayMethods)
        } else {
            copyAugment(value, arrayMethods, arrayKeys)
        }
        this.observeArray(value)
    }
};

function protoAugment (target, src: Object) {
  target.__proto__ = src
}
// node_modules/vue/src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

而改變了Array.__proto__多少方法呢?

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
    const original = arrayProto[method]
    def(arrayMethods, method, function mutator(...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
    })
})
// node_modules/vue/src/core/util/lang.js
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    })
}

從上面代碼分析可以知道,Vue劫持了Array的'push','pop','shift', 'unshift', 'splice', 'sort','reverse'方法,一旦運行了這些方法,會主動觸發(fā):

  • 調(diào)用Array原來的方法進(jìn)行調(diào)用,然后返回Array原來的方法的返回值,如Array.push調(diào)用后的返回值
  • 進(jìn)行observeArray的響應(yīng)式設(shè)置,更新新設(shè)置的item(可能為Object,需要設(shè)置響應(yīng)式)
  • 手動觸發(fā)ob.dep.notify(),觸發(fā)對應(yīng)的Watcher更新,達(dá)到響應(yīng)式自動更新的目的

渲染W(wǎng)atcher依賴收集流程分析

僅僅分析最簡單的渲染W(wǎng)atcher依賴收集的流程,實際上并不是只有渲染W(wǎng)atcher一種

圖片圖片

渲染W(wǎng)atcher派發(fā)更新流程分析

圖片圖片

computed依賴收集和派發(fā)更新分析

測試代碼

<div>{{myName}}</div>

// { [key: string]: Function | { get: Function, set: Function } }
computed: {
  myName: function() {
    // 沒有set()方法,只有g(shù)et()方法
    return this.firstName + this.lastName;
  }
}

依賴收集流程圖分析

圖片圖片

依賴收集代碼分析

computedWatcher初始化

Vue.prototype._init初始化時,會調(diào)用initState()->initComputed(),從而進(jìn)行computed數(shù)據(jù)的初始化

// node_modules/vue/src/core/instance/state.js
function initComputed(vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null)

    for (const key in computed) {
        const userDef = computed[key];
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions //{ lazy: true }
        )
        defineComputed(vm, key, userDef);
    }
}

從上面代碼可以知道,最終為每一個computed監(jiān)聽的數(shù)據(jù)建立一個Watcher,一個數(shù)據(jù)對應(yīng)一個computed Watcher,傳入{ lazy: true },然后調(diào)用defineComputed()方法

export function defineComputed(target: any, key: string, userDef: Object | Function) {
    // 為了減少分支判斷,方便理解,統(tǒng)一假設(shè)userDef傳入Function
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = noop;
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            if (watcher.dirty) {
                watcher.evaluate()
            }
            if (Dep.target) {
                watcher.depend()
            }
            return watcher.value
        }
    }
}

從上面代碼可以知道,最終defineComputed是進(jìn)行了Object.defineProperty的數(shù)據(jù)劫持,一般在computed中都只寫get()方法,即

computed: {
  myName: function() {
    // 沒有set()方法,只有g(shù)et()方法
    return this.firstName + this.lastName;
  }
}

而回到上面代碼的分析,defineComputed劫持了computed的get()方法,最終返回watcher.value

渲染W(wǎng)atcher觸發(fā)ComputedWatcher的get()方法執(zhí)行

當(dāng)界面上<template>{myName}</template>渲染myName的時候,會觸發(fā)myName的get()方法,由于Object.defineProperty的數(shù)據(jù)劫持,會先調(diào)用

  • watcher.evaluate()->watcher.get()(從下面的代碼可以得出這樣的推導(dǎo)關(guān)系)
  • watcher.depend()
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
    if (watcher.dirty) {
        // evaluate () {
        //     this.value = this.get()
        //     this.dirty = false
        // }
        watcher.evaluate()
    }
    if (Dep.target) {
        // depend() {
        //     let i = this.deps.length
        //     while (i--) {
        //         this.deps[i].depend()
        //     }
        // }
        watcher.depend()
    }
    return watcher.value
}
// watcher.js
get() {
   //  function pushTarget (target: ?Watcher) {
   //    targetStack.push(target)
   //    Dep.target = target
        // }
    pushTarget(this);
    let value;
    const vm = this.vm;
    try {
        // this.getter = return this.firstName + this.lastName;
        value = this.getter.call(vm, vm);
    } catch (e) {} 
    finally {
        if (this.deep) { // watch類型的watcher才能配置這個參數(shù)
            traverse(value);
        }
        popTarget();
        this.cleanupDeps();
    }
    return value;
}

從上面的代碼可以知道,當(dāng)調(diào)用watcher.evaluate()->watcher.get()的時候,會調(diào)用:

  • pushTarget(this):將目前的Dep.target 切換到Computed Watcher
  • this.getter.call(vm, vm):觸發(fā)this.firstName對應(yīng)的get()方法和this.lastName對應(yīng)的get()方法。由下面的依賴收集代碼可以知道,此時this.firstName和this.lastName持有的Dep會進(jìn)行dep.addSub(this),收集該Computed Watcher
  • popTarget():將目前的Dep.target恢復(fù)到上一個狀態(tài)
  • cleanupDeps():更新Computed Watcher的所有依賴關(guān)系,將無效的依賴關(guān)系刪除(比如v-if造成的依賴關(guān)系不用再依賴)
  • 最終返回myName= return this.firstName + this.lastName;

watcher.evaluate():求值 + 更新依賴 + 將涉及到的響應(yīng)式對象firstName和lastName關(guān)聯(lián)到Computed Watcher

export function defineReactive(obj: Object, key: string, val: any, ...args) {
    const dep = new Dep()
    let childOb = !shallow && observe(val)

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    childOb.dep.depend()
                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        }
    })
}

// Dep.js
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}

// watcher.js
addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
        this.newDepIds.add(id)
        this.newDeps.push(dep)
        if (!this.depIds.has(id)) {
            dep.addSub(this)
        }
    }
}

回到myName的get()方法,即下面的代碼,我們剛剛分析了watcher.evaluate(),那么我們接下來還調(diào)用了myName中watcher.depend()我們從上面的代碼知道,這個方法主要是用來收集依賴的,此時的Dep.target是渲染W(wǎng)atcher,computed Watcher會進(jìn)行自身的depend(),本質(zhì)是拿出自己所有記錄的Dep(為了方便理解,我們理解Dep就是一個響應(yīng)式對象的代理),computed Watcher拿出自己記錄的所有的deps[i],然后調(diào)用它們的depend()方法,從而完成這些響應(yīng)式對象(firstName和lastName)與渲染W(wǎng)atcher的關(guān)聯(lián),最后返回watcher.value

const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
    if (watcher.dirty) {
        // 上面分析觸發(fā)了watcher.get()方法
        // 得到對應(yīng)的watcher.value
        // 收集了firstName+lastName和computerWatcher的綁定
        watcher.evaluate();
        // 將目前的Dep.target切換到渲染W(wǎng)atcher
    }
    if (Dep.target) {
        // depend() {
        //     let i = this.deps.length
        //     while (i--) {
        //         this.deps[i].depend()
        //     }
        // }
        watcher.depend()
    }
    return watcher.value
}

// watcher.js
depend() {
    // this.deps是從cleanupDeps()中
    // this.deps = this.newDeps來的
    // this.newDeps是通過addDep()來的
    let i = this.deps.length
    while (i--) {
        this.deps[i].depend()
    }
}

// Dep.js
depend() {
    if (Dep.target) {
        Dep.target.addDep(this)
    }
}

派發(fā)更新流程圖分析

圖片圖片

派發(fā)更新代碼分析

computed: {
  myName: function() {
    // 沒有set()方法,只有g(shù)et()方法
    return this.firstName + this.lastName;
  }
}

當(dāng)this.firstName發(fā)生改變時,會觸發(fā)this.firstName.dep.subs.notify()功能,也就是觸發(fā)剛剛注冊的兩個Watcher: 渲染W(wǎng)atcher和Computed Watcher,首先觸發(fā)的是Computed Watcher的notify()方法,由下面的代碼可以知道,只執(zhí)行this.dirty=true

update () {
    // Computed Watcher的this.lazy都為true
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

然后觸發(fā)渲染W(wǎng)atcher,觸發(fā)整個界面進(jìn)行渲染,從而觸發(fā)該computed[key]的get()方法執(zhí)行,也就是myName的get()方法執(zhí)行,由依賴收集的代碼可以知道,最終執(zhí)行為

const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
    if (watcher.dirty) {
        // 上面分析觸發(fā)了watcher.get()方法
        // 得到對應(yīng)的watcher.value
        watcher.evaluate();
    }
    if (Dep.target) {
        // depend() {
        //     let i = this.deps.length
        //     while (i--) {
        //         this.deps[i].depend()
        //     }
        // }
        watcher.depend()
    }
    return watcher.value
}

從上面的分析可以知道,computed[key]的get()先收集了一波依賴:

  • watcher.evaluate():求值watcher.value + 更新依賴 + 將涉及到的響應(yīng)式對象關(guān)聯(lián)到Computed Watcher
  • watcher.depend():將涉及到的響應(yīng)式對象關(guān)聯(lián)到當(dāng)前的Dep.target,即渲染W(wǎng)atcher

然后返回了對應(yīng)的值watcher.value

computedWatcher一般無set方法,因此觸發(fā)派發(fā)更新就是觸發(fā)渲染W(wǎng)atcher/其它Watcher持有computed進(jìn)行重新渲染,從而觸發(fā)computed的get方法,收集最新依賴以及獲取最新值

watch依賴收集和派發(fā)更新分析

watch流程圖跟computed流程大同小異,因此watch只做源碼分析

測試代碼

watch支持多種模式的監(jiān)聽方式,比如傳入一個回調(diào)函數(shù),比如傳入一個方法名稱,比如傳入一個Object,配置參數(shù)

// { [key: string]: string | Function | Object | Array }
watch: {
    a: function (val, oldVal) {},
    b: 'someMethod', // 方法名
    c: {
      handler: function (val, oldVal) {}, // 值改變時的回調(diào)方法
      deep: true, // 深度遍歷
      immediate: true // 馬上回調(diào)一次
    },
    // 你可以傳入回調(diào)數(shù)組,它們會被逐一調(diào)用
    e: [
      'handle1', // 方式1
      function handle2 (val, oldVal) {}, // 方式2
            { // 方式3
        handler: function (val, oldVal) {},
        deep: true,
        immediate: true
        },
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) {}
}

初始化watch

export function initState(vm: Component) {
    if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
    }
}
function initWatch(vm: Component, watch: Object) {
    for (const key in watch) {
        const handler = watch[key];
        // 處理watch:{b: [三種形式都允許]}的形式
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i]);
            }
        } else {
            createWatcher(vm, key, handler);
        }
    }
}
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {
    if (isPlainObject(handler)) {
        // 處理watch:{b: {handler: 處理函數(shù), deep: true, immediate: true}}的形式
        options = handler
        handler = handler.handler
    }
    if (typeof handler === 'string') {
        // 處理watch: {b: 'someMethod'}的形式
        handler = vm[handler]
    }
    return vm.$watch(expOrFn, handler, options)
}

從上面的代碼可以看出,初始化時,會進(jìn)行watch中各種參數(shù)的處理,將3種不同類型的watch回調(diào)模式整理成為規(guī)范的模式,最終調(diào)用Vue.prototype.$watch進(jìn)行new Watcher的構(gòu)建

Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {
    const vm: Component = this
    // cb是回調(diào)方法,如果還是對象,則使用createWatcher拆出來里面的對象
    if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
    }
    options.user = true
    // 建立一個watch類型的Watcher
    // expOrFn: getter
    // cb: 注冊的回調(diào)
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
        // optinotallow={immediate:true}的分支邏輯
        pushTarget()
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
        popTarget()
    }
    return function unwatchFn() {
        watcher.teardown()
    }
}

依賴收集代碼分析

新建Watcher的時候, 在constructor()中會觸發(fā)

class watcher {
    constructor() {
    // watch的key
    this.getter = parsePath(expOrFn);
    this.value = this.lazy?undefined:this.get();
}

const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\d]`)
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

從上面的代碼可以知道,最終this.getter調(diào)用的還是傳入的obj[key],從下面的get()方法可以知道,賦值this.getter后,會觸發(fā)get()方法,從而觸發(fā)this.getter.call(vm, vm),因此最終this.getter得到的就是vm[key]

get() {
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    if (this.deep) {
        traverse(value); // 深度遍歷數(shù)組/對象,實現(xiàn)
    }
    popTarget()
    this.cleanupDeps()
    return value
}

// traverse.js
export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

上面代碼的步驟可以概括為

  • pushTarget:修復(fù)當(dāng)前的Dep.target為當(dāng)前的watch類型的Watcher
  • this.getter:返回當(dāng)前的vm[key],同時觸發(fā)vm[key]的響應(yīng)式劫持get()方法,從而觸發(fā)vm[key]持有的Dep對象啟動dep.depend()進(jìn)行依賴收集(如下面代碼所示),vm[key]持有的Dep對象將當(dāng)前的watch類型的Watcher收集到vm[key]中,下次vm[key]發(fā)生變化時,會觸發(fā)watch類型的Watcher進(jìn)行callback的回調(diào)
  • traverse(value):深度遍歷,會訪問每一個Object的key,由于每一個Object的key之前在initState()的時候已經(jīng)使用Object.defineProperty()進(jìn)行g(shù)et方法的劫持,因此觸發(fā)它們對應(yīng)的getter方法,進(jìn)行dep.depend()收集當(dāng)前的watch類型的Watcher,從而實現(xiàn)改變Object內(nèi)部深層的某一個key的時候會回調(diào)watch類型的Watcher。沒有加deep=true的時候,watch類型的Watcher只能監(jiān)聽Object的改變,比如watch:{curData: function(){}},只有this.curData=xxx,才會觸發(fā)watch,this.curData.children=xxx是不會觸發(fā)的
  • popTarget:恢復(fù)Dep.target為上一個狀態(tài)
  • cleanupDeps:更新依賴關(guān)系
  • 返回值value,依賴收集結(jié)束,watch類型的Watcher初始化結(jié)束
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
            dep.depend()
            if (childOb) {
                childOb.dep.depend()
                if (Array.isArray(value)) {
                    dependArray(value)
                }
            }
        }
        return value
    }
})

派發(fā)更新代碼分析

當(dāng)watcher的值發(fā)生改變時,會觸發(fā)dep.subs.notify()方法,從上面的分析可以知道,最終會調(diào)用watcher.run()方法

run() {
    if (this.active) {
        const value = this.get()
        if (
            value !== this.value ||
            isObject(value) ||
            this.deep
        ) {
            // set new value
            const oldValue = this.value
            this.value = value
            if (this.user) {
                const info = `callback for watcher "${this.expression}"`
                invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
            } else {
                this.cb.call(this.vm, value, oldValue)
            }
        }
    }
}

由于watch類型的Watcher傳入了this.user=true,因此會觸發(fā)invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info),將新值和舊值一起回調(diào),比如

watch: {
  myObject: function(value, oldValue) {//新值和舊值}
}

watchOptions幾種模式分析

deep=true

// watcher.js
get() {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
        value = this.getter.call(vm, vm)
    } catch (e) {
        if (this.user) {
            handleError(e, vm, `getter for watcher "${this.expression}"`)
        } else {
            throw e
        }
    } finally {
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {
            traverse(value)
        }
        popTarget()
        this.cleanupDeps()
    }
    return value
}

在get()方法中進(jìn)行對象的深度key的遍歷,觸發(fā)它們的getter()方法,進(jìn)行依賴的收集,可以實現(xiàn)

watch: {
  myObject: {
    deep: true,
    handler: function(value, oldValue) {//新值和舊值}
  }
}

this.myObject.a = 2;

雖然上面的例子只是監(jiān)聽了myObject,但是由于加入deep=true,因此this.myObject.a也會觸發(fā)watcher.run(),如下面代碼所示,由于this.deep=true,因此會回調(diào)cb(value, oldValue)

run() {
    if (this.active) {
        const value = this.get()
        if (
            value !== this.value ||
            isObject(value) ||
            this.deep
        ) {
            // set new value
            const oldValue = this.value
            this.value = value
            if (this.user) {
                const info = `callback for watcher "${this.expression}"`
                invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
            } else {
                this.cb.call(this.vm, value, oldValue)
            }
        }
    }
}

immediate=true

從下面代碼可以知道,當(dāng)聲明immediate=true的時候,初始化Watcher,會馬上調(diào)用invokeWithErrorHandling(cb, vm, [watcher.value], vm, info),即cb的回調(diào)

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
        return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
        const info = `callback for immediate watcher "${watcher.expression}"`
        pushTarget()
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
        popTarget()
    }
    return function unwatchFn() {
        watcher.teardown()
    }
}

watch: {
    myObject:
    {
        immediate: true,
          handler: function() {...初始化馬上觸發(fā)一次}
    }
}

sync=true

如果聲明了sync=true,在dep.sub.notify()中,會馬上執(zhí)行,如果沒有聲明sync=true,會推入隊列中,等到下一個nextTick周期才會執(zhí)行

update() {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true
    } else if (this.sync) {
        this.run()
    } else {
        queueWatcher(this)
    }
}

export function queueWatcher(watcher: Watcher) {
    const id = watcher.id
    if (has[id] == null) {
        has[id] = true
        if (!flushing) {
            queue.push(watcher)
        } else {
            // if already flushing, splice the watcher based on its id
            // if already past its id, it will be run next immediately.
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {
                i--
            }
            queue.splice(i + 1, 0, watcher)
        }
        // queue the flush
        if (!waiting) {
            waiting = true

            if (process.env.NODE_ENV !== 'production' && !config.async) {
                flushSchedulerQueue()
                return
            }
            nextTick(flushSchedulerQueue)
        }
    }
}

責(zé)任編輯:武曉燕 來源: 量子前端
相關(guān)推薦

2025-05-06 01:14:00

系統(tǒng)編程響應(yīng)式

2022-06-26 00:00:02

Vue3響應(yīng)式系統(tǒng)

2021-01-15 08:10:26

Vue開發(fā)模板

2025-03-17 00:21:00

2023-10-18 10:55:55

HashMap

2020-06-09 11:35:30

Vue 3響應(yīng)式前端

2019-07-01 13:34:22

vue系統(tǒng)數(shù)據(jù)

2021-01-22 11:47:27

Vue.js響應(yīng)式代碼

2022-07-14 08:22:48

Computedvue3

2021-10-11 11:58:41

Channel原理recvq

2021-10-09 19:05:06

channelGo原理

2023-05-29 08:12:38

2017-08-30 17:10:43

前端JavascriptVue.js

2022-09-19 18:49:01

偵聽器異步組件

2023-11-28 17:49:51

watch?computed?性能

2022-04-24 11:06:54

SpringBootjar代碼

2022-08-26 13:24:03

version源碼sources

2021-07-08 10:08:03

DvaJS前端Dva

2022-02-25 14:19:56

依賴管理前端命令

2023-10-07 08:35:07

依賴注入Spring
點贊
收藏

51CTO技術(shù)棧公眾號

九九热在线免费观看| 69久久精品无码一区二区| 青青青手机在线视频观看| 久久精品天堂| 久久久国产精品亚洲一区| 美女日批在线观看| 三上悠亚国产精品一区二区三区| 国产精品视频看| 高清视频一区二区三区| 日本熟妇一区二区三区| 亚洲蜜桃视频| 日韩电影第一页| 性生活免费在线观看| 黄页网站在线| 国产日韩一级二级三级| 动漫精品视频| 成人免费一区二区三区| 亚洲激情社区| 久久精品小视频| 一区二区视频观看| 国产精品一区二区美女视频免费看| 欧美色道久久88综合亚洲精品| 伊人久久av导航| 亚洲人在线观看视频| 精品一区中文字幕| 欧洲成人在线观看| 欧美黑人精品一区二区不卡| 精品一区电影| 亚洲精品自拍视频| 四川一级毛毛片| 成人深夜福利| 国内在线观看一区二区三区| 亚洲欧美中文日韩在线| 无码人妻丰满熟妇啪啪网站| 开心久久婷婷综合中文字幕| 一本色道久久综合亚洲aⅴ蜜桃 | 91精品人妻一区二区三区蜜桃2| 久久sese| 日韩欧美精品中文字幕| 久久精品无码中文字幕| 黄色免费网站在线观看| 国产欧美日韩亚州综合| 免费影院在线观看一区| 日韩性xxxx| 国产精品99久久久久久有的能看| 国产精品视频一区二区高潮| 日本免费在线观看视频| 国产亚洲精品v| 久久久久久美女| 久久一区二区三| 亚洲国产日韩欧美在线| 久久精品2019中文字幕| 四虎成人免费影院| 国产一区二区三区天码| 亚洲系列中文字幕| 人妻av无码一区二区三区| 亚洲香蕉视频| 亚洲欧洲在线播放| 精品成人无码一区二区三区| 国产一区二区三区91| 亚洲性av网站| 91无套直看片红桃在线观看| 久久国产精品亚洲人一区二区三区| 在线看日韩av| 少妇视频在线播放| 欧美大人香蕉在线| 久久精品国产91精品亚洲| 免费成年人视频在线观看| 亚洲男女av一区二区| 欧美日本高清视频| 亚洲国产综合久久| 国产农村妇女毛片精品久久莱园子 | 久久亚洲图片| 国产精品黄视频| 91tv国产成人福利| 国产精品一区免费在线观看| 国产成人免费观看| 婷婷在线观看视频| 国产鲁鲁视频在线观看免费| 成人免费高清视频| 九九九九九精品| 黄色av免费在线观看| 国产精品女上位| 欧美少妇一区二区三区| 爱情岛论坛亚洲品质自拍视频网站| 五月激情综合网| 成人免费视频久久| 国产一区二区视频在线看| 精品国产乱码久久久久久久久| 亚洲av成人片无码| 国内亚洲精品| 欧美精品一区二区免费| 国产91露脸中文字幕在线| 乱老熟女一区二区三区| 亚洲欧美亚洲| 欧美亚洲国产精品| 在线观看中文字幕码| 国产精品一二三区| 蜜桃视频在线观看成人| 丝袜美腿美女被狂躁在线观看| 亚洲精品日韩专区silk| 成人观看免费完整观看| 日本精品久久| 亚洲精品久久久一区二区三区 | 欧美韩国日本一区| 精品久久久无码人妻字幂| 香蕉伊大人中文在线观看| 欧美日韩在线播放| a天堂视频在线观看| 三级电影一区| 欧洲成人免费视频| www.国产麻豆| 国产一区二区你懂的| 国产精品极品尤物在线观看 | 日韩成人在线视频| 欧美色视频一区二区三区在线观看| 影音先锋久久资源网| 国产日韩在线看| 天堂а在线中文在线无限看推荐| 亚洲视频精选在线| 免费观看成人在线视频| 红杏视频成人| 九九九久久国产免费| 亚洲视屏在线观看| 97成人超碰视| 欧美视频在线第一页| 成人精品动漫| 亚洲片av在线| 日韩福利片在线观看| 国产高清在线精品| 在线播放 亚洲| 欧美日韩在线精品一区二区三区激情综合 | 国产视频在线观看视频| 中文字幕不卡在线| aaaaaa亚洲| 天堂日韩电影| 国内精品视频一区| 亚洲精品人妻无码| 亚洲精品一二三| 欧美激情第3页| 人人狠狠综合久久亚洲婷| 日本中文字幕成人| 青梅竹马是消防员在线| 精品magnet| av在线播放网址| 在线欧美视频| 国产二区不卡| 免费av不卡在线观看| 日韩免费电影网站| 久久久久久久久久久久久久久久久| 狠狠色狠狠色综合系列| 在线观看免费91| 欧美aaaaaa| 日韩中文字幕视频| 96亚洲精品久久久蜜桃| 自拍视频在线观看一区二区| 日本中文字幕精品—区二区| 欧美激情777| 国产在线一区二区三区| 国产激情视频在线| 欧美一区二区三区四区五区 | 欧美日韩一区二区区别是什么 | 久久精品夜色噜噜亚洲aⅴ| 成人在线免费在线观看| 精品久久久久久久| 国产精品日韩电影| 日本视频在线播放| 日韩亚洲欧美综合| 国产午夜视频在线| 91色视频在线| 国产高清视频网站| 亚洲区综合中文字幕日日| 亚洲xxxx做受欧美| 高清视频在线观看三级| 亚洲精品自在久久| 亚洲在线观看av| 一区二区三区在线高清| 人妻换人妻a片爽麻豆| 国产精品毛片在线看| 涩涩涩999| 精品视频在线观看免费观看| 国内精品久久久久久| 欧美婷婷久久五月精品三区| 欧美三级中文字幕在线观看| 国产成人av免费在线观看| 成人福利视频网站| 国产激情在线观看视频| 午夜久久免费观看| 国产九色91| 日韩一区二区三区免费| 毛片精品免费在线观看| 五月婷婷六月激情| 欧美日本一区二区三区四区| 黄色一级片中国| 久久婷婷一区二区三区| 手机av在线网站| 国产精品婷婷| 日本黄色a视频| 免费看久久久| 成人女保姆的销魂服务| 中文字幕在线免费观看视频| 精品国模在线视频| 亚州男人的天堂| 欧美酷刑日本凌虐凌虐| 天天操天天操天天操天天| 国产亚洲欧美一级| 北京富婆泄欲对白| 极品少妇xxxx精品少妇| 波多野结衣家庭教师视频| 欧美午夜不卡| 一本一道久久a久久精品综合| 激情小说一区| 成人激情黄色网| 吞精囗交69激情欧美| 久久久久国产精品免费网站| av在线免费观看网| 日韩精品免费电影| www.av网站| 欧美高清视频在线高清观看mv色露露十八| 六月丁香在线视频| 亚洲激情网站免费观看| av网站免费在线看| zzijzzij亚洲日本少妇熟睡| 免费在线观看污网站| 日本免费新一区视频| 每日在线更新av| 国产精品v欧美精品v日本精品动漫| 亚洲精品成人三区| 国产精品欧美三级在线观看| 古典武侠综合av第一页| 久久久久久爱| 91免费视频国产| 91在线成人| 国产成人精品免费久久久久| 三妻四妾完整版在线观看电视剧 | 欧美日韩国产综合视频在线| 视频精品一区| 91精品视频免费观看| 高清欧美日韩| 国产精品久久久久久久久久久新郎 | 国产又黄又猛又粗又爽的视频| 亚洲一区二区网站| 精品久久久久久久久久中文字幕| 欧美午夜在线| 路边理发店露脸熟妇泻火| 久久精品久久久| 中日韩在线视频| 97精品97| 日本美女爱爱视频| 韩国亚洲精品| 国产免费黄色一级片| 日韩午夜一区| 国产又黄又大又粗视频| 另类激情亚洲| 人妻无码视频一区二区三区| 日韩福利电影在线| 蜜臀视频一区二区三区| 日韩国产欧美三级| 99re精彩视频| 国产在线精品视频| 日韩欧美色视频| 懂色av中文字幕一区二区三区 | 中国av一区二区三区| 国产免费嫩草影院| 亚洲男人的天堂网| 国产真实的和子乱拍在线观看| 亚洲高清免费视频| 精品欧美一区二区三区免费观看| 欧美日韩中国免费专区在线看| 久久亚洲精品石原莉奈| 欧美三区在线视频| 性一交一乱一精一晶| 日韩av一区在线观看| 国产视频福利在线| 久久久久99精品久久久久| 青春草在线免费视频| 欧美中文字幕在线视频| 国产乱子精品一区二区在线观看| 91在线免费视频| 久久久亚洲欧洲日产| 欧洲精品一区色| 亚洲精品久久| 鲁一鲁一鲁一鲁一澡| 奇米888四色在线精品| 日本人dh亚洲人ⅹxx| 91丝袜高跟美女视频| 天海翼在线视频| 亚洲国产成人va在线观看天堂| 日本免费在线观看视频| 在线不卡欧美精品一区二区三区| 亚洲第一视频在线播放| 亚洲欧美中文另类| 日本在线视频www鲁啊鲁| 欧美专区在线播放| 国产精品亚洲综合在线观看| 久久久亚洲综合网站| 亚洲精品99| 国产 porn| av男人天堂一区| 国产高清视频免费在线观看| 精品欧美激情精品一区| 国产精品高潮呻吟久久久| 亚洲精品电影久久久| av在线导航| 国产精品pans私拍| 97视频一区| 男女啪啪的视频| 久久一二三四| 国产成人精品无码片区在线| 中文字幕在线观看一区| 极品国产91在线网站| 亚洲成人黄色在线| www视频在线免费观看| 日韩免费观看网站| 国产精品色在线网站| 干日本少妇视频| 久久精品国产一区二区三| 黄色a一级视频| 亚洲一区在线观看免费观看电影高清 | 久久综合之合合综合久久| 日本精品一区二区三区在线| 丁香5月婷婷久久| 色爽爽爽爽爽爽爽爽| 蜜臀av一区二区在线观看| 国产精品久久久久9999爆乳| 欧美aaaaa成人免费观看视频| 亚洲国产精品无码久久久久高潮| 亚洲欧美另类久久久精品| 午夜视频网站在线观看| 亚洲欧美成人网| 深夜av在线| 精品免费日产一区一区三区免费| 欧美高清日韩| 天天操夜夜操很很操| 亚洲欧洲av在线| 一级全黄裸体免费视频| 综合网中文字幕| 素人啪啪色综合| 日韩欧美在线电影| 日韩精品亚洲专区| 三上悠亚影音先锋| 一本色道久久综合精品竹菊| 亚州av在线播放| 欧美一区二区视频97| 天堂99x99es久久精品免费| www.浪潮av.com| 91蜜桃在线免费视频| 国产一级精品视频| 精品视频中文字幕| 亚洲午夜天堂| 青青成人在线| 首页欧美精品中文字幕| 色屁屁草草影院ccyy.com| 欧美优质美女网站| 在线观看二区| 成人乱色短篇合集| 91成人精品视频| 男插女视频网站| 亚洲国产精品一区二区www| 亚洲色图欧美视频| 日本精品va在线观看| 精品久久精品| 亚洲在线观看网站| 亚洲一区二区三区在线播放| 色哟哟国产精品色哟哟| 欧美一级片一区| 清纯唯美综合亚洲| 人妻激情偷乱视频一区二区三区| 亚洲美女在线国产| 国产刺激高潮av| 日本aⅴ大伊香蕉精品视频| 欧美欧美黄在线二区| 中文字幕中文在线| 一区二区三区四区视频精品免费| 日韩在线视频观看免费| 热99精品只有里视频精品| 凹凸成人精品亚洲精品密奴| av噜噜在线观看| 亚洲成av人**亚洲成av**| 欧美精品a∨在线观看不卡| 国产精品专区一| 尹人成人综合网| 中文字幕黄色网址| 精品国产一区二区亚洲人成毛片 | 午夜精品福利一区二区蜜股av| 午夜福利理论片在线观看| 国产精品久久999| 黄色av日韩| 你懂得视频在线观看| 日韩欧美亚洲一区二区| 成人开心激情| 国产一级大片免费看| 国产香蕉久久精品综合网| 国产国语亲子伦亲子| 欧美在线性视频| 亚洲欧美偷拍自拍| av永久免费观看| 亚洲精品成人av| 亚洲成人五区| 国产高潮免费视频|