国产99久久精品_欧美日本韩国一区二区_激情小说综合网_欧美一级二级视频_午夜av电影_日本久久精品视频

最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當前位置: 首頁 - 科技 - 知識百科 - 正文

Vue源碼探究之狀態初始化

來源:懂視網 責編:小采 時間:2020-11-27 22:03:49
文檔

Vue源碼探究之狀態初始化

Vue源碼探究之狀態初始化:繼續隨著核心類的初始化展開探索其他的模塊,這一篇來研究一下Vue的狀態初始化。這里的狀態初始化指的就是在創建實例的時候,在配置對象里定義的屬性、數據變量、方法等是如何進行初始處理的。由于隨后的數據更新變動都交給觀察系統來負責,所以在事先弄明白
推薦度:
導讀Vue源碼探究之狀態初始化:繼續隨著核心類的初始化展開探索其他的模塊,這一篇來研究一下Vue的狀態初始化。這里的狀態初始化指的就是在創建實例的時候,在配置對象里定義的屬性、數據變量、方法等是如何進行初始處理的。由于隨后的數據更新變動都交給觀察系統來負責,所以在事先弄明白

繼續隨著核心類的初始化展開探索其他的模塊,這一篇來研究一下Vue的狀態初始化。這里的狀態初始化指的就是在創建實例的時候,在配置對象里定義的屬性、數據變量、方法等是如何進行初始處理的。由于隨后的數據更新變動都交給觀察系統來負責,所以在事先弄明白了數據綁定的原理之后,就只需要將目光集中在這一部分。

來仔細看看在核心類中首先執行的關于 state 部分的源碼:

initState

// 定義并導出initState函數,接收參數vm
export function initState (vm: Component) {
 // 初始化實例的私有屬性_watchers
 // 這就是在觀察系統里會使用到的存儲所有顯式監視器的對象
 vm._watchers = []
 // 獲取實例的配置對象
 const opts = vm.$options
 // 如果定義了props,則初始化props
 if (opts.props) initProps(vm, opts.props)
 // 如果定義了methods,則初始化methods
 if (opts.methods) initMethods(vm, opts.methods)
 // 如果定義了data,則初始化data
 if (opts.data) {
 initData(vm)
 } else {
 // 否則初始化實例的私有屬性_data為空對象,并開啟觀察
 observe(vm._data = {}, true /* asRootData */)
 }
 // 如果定義了computed,則初始化計算屬性
 if (opts.computed) initComputed(vm, opts.computed)
 // 如果定義了watch并且不是nativeWatch,則初始化watch
 // nativeWatch是火狐瀏覽器下定義的對象的原型方法
 if (opts.watch && opts.watch !== nativeWatch) {
 initWatch(vm, opts.watch)
 }
}

這段代碼非常直白,主要用來執行配置對象里定義的了狀態的初始化。這里分別有 props、data、methods、computed、watch 五個配置對象,分別有各自的初始化方法。在仔細研究它們的具體實現之前,先來看一段將在各個初始化函數里用到的輔助函數。

// 定義共享屬性定義描述符對象sharedPropertyDefinition
// 描述符對象的枚舉和可配置屬性都設置為true
// get、set方法設置為空函數
const sharedPropertyDefinition = {
 enumerable: true,
 configurable: true,
 get: noop,
 set: noop
}

// 定義并導出proxy函數,該函數用來為在目標對象上定義并代理屬性
// 接收目標對象target,路徑鍵名sourceKey,屬性鍵名三個參數
export function proxy (target: Object, sourceKey: string, key: string) {
 // 設置屬性描述符對象的get方法
 sharedPropertyDefinition.get = function proxyGetter () {
 return this[sourceKey][key]
 }
 // 設置屬性描述性對象的set犯法
 sharedPropertyDefinition.set = function proxySetter (val) {
 this[sourceKey][key] = val
 }
 // 在目標對象上定義屬性
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

proxy 函數的定義非常重要,在下面要探究的各個初始化函數中它,它會將我們在配置對象中設置的屬性全部定義到實例對象中,但是我們對這些屬性的操作是通過各部分相應的代理屬性上來執行的。get 和 set 方法的實現非常明白的表示出這一過程,然后再將屬性定義到實例中。由這個函數作為基礎,繼續來看看其他五個狀態的初始化函數的內容。

initProps

// 定義initProps函數,接收vm,propsOptions兩個參數
function initProps (vm: Component, propsOptions: Object) {
 // 賦值propsData,propsData是全局擴展傳入的賦值對象
 // 在使用extend的時候會用到,實際開發里運用較少
 const propsData = vm.$options.propsData || {}
 // 定義實例的_props私有屬性,并賦值給props
 const props = vm._props = {}
 // 緩存prop鍵,以便將來props更新可以使用Array而不是動態對象鍵枚舉進行迭代。
 // cache prop keys so that future props updates can iterate using Array
 // instead of dynamic object key enumeration.
 const keys = vm.$options._propKeys = []
 // 是否是根實例
 const isRoot = !vm.$parent
 // 對于非根實例,關閉觀察標識
 // root instance props should be converted
 if (!isRoot) {
 toggleObserving(false)
 }
 // 遍歷props配置對象
 for (const key in propsOptions) {
 // 向緩存鍵值數組中添加鍵名
 keys.push(key)
 // 驗證prop的值,validateProp執行對初始化定義的props的類型檢查和默認賦值
 // 如果有定義類型檢查,布爾值沒有默認值時會被賦予false,字符串默認undefined
 // 對propsOptions的比較也是在使用extend擴展時才有意義
 // 具體實現可以參考 src/core/util/props.js,沒有難點這里不詳細解釋
 const value = validateProp(key, propsOptions, propsData, vm)

 // 非生產環境下進行檢查和提示
 /* istanbul ignore else */
 if (process.env.NODE_ENV !== 'production') {
 // 進行鍵名的轉換,將駝峰式轉換成連字符式的鍵名
 const hyphenatedKey = hyphenate(key)
 // 對與保留變量名沖突的鍵名給予提示
 if (isReservedAttribute(hyphenatedKey) ||
 config.isReservedAttr(hyphenatedKey)) {
 warn(
 `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
 vm
 )
 }
 // 對屬性建立觀察,并在直接使用屬性時給予警告
 defineReactive(props, key, value, () => {
 if (vm.$parent && !isUpdatingChildComponent) {
 warn(
 `Avoid mutating a prop directly since the value will be ` +
 `overwritten whenever the parent component re-renders. ` +
 `Instead, use a data or computed property based on the prop's ` +
 `value. Prop being mutated: "${key}"`,
 vm
 )
 }
 })
 } else {
 // 非生產環境下直接對屬性進行存取器包裝,建立依賴觀察
 defineReactive(props, key, value)
 }
 // 使用Vue.extend()方法擴展屬性時,已經對靜態屬性進行了代理
 // 這里只需要針對實例化時的屬性執行代理操作
 // static props are already proxied on the component's prototype
 // during Vue.extend(). We only need to proxy props defined at
 // instantiation here.
 // 當實例上沒有同名屬性時,對屬性進行代理操作
 // 將對鍵名的引用指向vm._props對象中
 if (!(key in vm)) {
 proxy(vm, `_props`, key)
 }
 }
 // 開啟觀察狀態標識
 toggleObserving(true)
}

initProps 函數的最主要內容有兩點,一是對定義的數據建立觀察,二是對數據進行代理,這就是私有變量 _props 的作用,之后獲取和設置的變量都是作為 _props 的屬性被操作。

另外初始化 props 的過程中有針對 extend 方法會使用到的 propsData 屬性的初始化。具體使用是在擴展對象時定義一些 props,然后在創建實例的過程中傳入 propsData 配置對象,擴展對象里相應的props屬性會接收 propsData 傳入的值。與在父組件傳入 props 的值類似,只是這里要顯式的通過 propsData 配置對象來傳入值。

initData

// 定義initData函數
function initData (vm: Component) {
 // 獲取配置對象的data屬性
 let data = vm.$options.data
 // 判斷data是否是函數
 // 若是函數則將getData函數的返回賦值給data和實例私有屬性_data
 // 否則直接將data賦值給實例_data屬性,并在無data時賦值空對象
 data = vm._data = typeof data === 'function'
 ? getData(data, vm)
 : data || {}
 // 如果data不是對象則將data賦值為空對象
 // 進一步保證data是對象類型
 if (!isPlainObject(data)) {
 data = {}
 // 在非生產環境下給出警告提示
 process.env.NODE_ENV !== 'production' && warn(
 'data functions should return an object:\n' +
 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
 vm
 )
 }
 // 實例對象代理data
 // proxy data on instance
 // 獲取所有data鍵值
 const keys = Object.keys(data)
 // 獲取配置對象的props
 const props = vm.$options.props
 // 獲取配置對象的methods
 const methods = vm.$options.methods
 // 遍歷keys
 let i = keys.length
 while (i--) {
 const key = keys[i]
 // 非生產環境給出與methods定義的方法名沖突的警告
 if (process.env.NODE_ENV !== 'production') {
 if (methods && hasOwn(methods, key)) {
 warn(
 `Method "${key}" has already been defined as a data property.`,
 vm
 )
 }
 }
 // 檢測是否與props沖突
 if (props && hasOwn(props, key)) {
 // 非生產環境給出沖突警告
 process.env.NODE_ENV !== 'production' && warn(
 `The data property "${key}" is already declared as a prop. ` +
 `Use prop default value instead.`,
 vm
 )
 // 沒有與props沖突并且非保留字時,代理鍵名到實例的_data對象上
 } else if (!isReserved(key)) {
 proxy(vm, `_data`, key)
 }
 }
 // 觀察數據
 // observe data
 observe(data, true /* asRootData */)
}

// 定義并導出getData函數,接受函數類型的data對象,和Vue實例對象
export function getData (data: Function, vm: Component): any {
 // pushTarget和popTarget是為了解決Vue依賴性檢測的缺陷可能導致冗余依賴性的問題
 // 具體可參閱 https://github.com/vuejs/vue/issues/7573
 // 此操作會設置Dep.target為undefined,在初始化option時調用dep.depend()也不會建立依賴
 // #7573 調用數據getter時禁用dep集合
 // #7573 disable dep collection when invoking data getters
 pushTarget()
 // 嘗試在vm上調用data函數并返回執行結果
 try {
 return data.call(vm, vm)
 } catch (e) {
 // 如果捕獲到錯誤則處理錯誤,并返回空對象
 handleError(e, vm, `data()`)
 return {}
 } finally {
 popTarget()
 }
}

與 props 的處理類似,initData 函數的作用也是為了對數據建立觀察的依賴關系,并且代理數據到私有變量 _data 上,另外包括了對 data 與其他配置對象屬性的鍵名沖突的檢測。

initComputed

// 設置computedWatcherOptions對象
const computedWatcherOptions = { computed: true }

// 定義initComputed函數,接受實例vm,和computed對象
function initComputed (vm: Component, computed: Object) {
 // $flow-disable-line
 // 定義watchers和實例_computedWatchers屬性,初始賦值空對象
 const watchers = vm._computedWatchers = Object.create(null)
 // 是否是服務器渲染,computed屬性在服務器渲染期間只能是getter
 // computed properties are just getters during SSR
 const isSSR = isServerRendering()

 // 遍歷computed
 for (const key in computed) {
 // 獲取用戶定義的值
 const userDef = computed[key]
 // 如果用戶定義的是函數則賦值給getter否則j將userDef.get方法賦值給getter
 const getter = typeof userDef === 'function' ? userDef : userDef.get
 // 非生產環境拋出缺少計算屬性錯誤警告
 if (process.env.NODE_ENV !== 'production' && getter == null) {
 warn(
 `Getter is missing for computed property "${key}".`,
 vm
 )
 }

 // 非服務器渲染下
 if (!isSSR) {
 // 為計算屬性創建內部監視器
 // create internal watcher for the computed property.
 watchers[key] = new Watcher(
 vm,
 getter || noop,
 noop,
 computedWatcherOptions
 )
 }

 // 組件定義的內部計算屬性已經在組件的原型上定義好了
 // 所以這里只要關注實例初始化時用戶定義的計算屬性
 // component-defined computed properties are already defined on the
 // component prototype. We only need to define computed properties defined
 // at instantiation here.
 // 鍵名非實例根屬性時,定義計算屬性,具體參照defineComputed函數
 if (!(key in vm)) {
 defineComputed(vm, key, userDef)
 // 非生產環境下,檢測與data屬性名的沖突并給出警告
 } else if (process.env.NODE_ENV !== 'production') {
 if (key in vm.$data) {
 warn(`The computed property "${key}" is already defined in data.`, vm)
 } else if (vm.$options.props && key in vm.$options.props) {
 warn(`The computed property "${key}" is already defined as a prop.`, vm)
 }
 }
 }
}

// 定義并導出defineComputed哈數
// 接收實例target,計算屬性鍵名key,計算屬性值userDef參數
export function defineComputed (
 target: any,
 key: string,
 userDef: Object | Function
) {
 // 在非服務器渲染下設置緩存
 const shouldCache = !isServerRendering()
 // 計算屬性值是函數時
 if (typeof userDef === 'function') {
 // 設置計算屬性的getter,setter為空函數
 sharedPropertyDefinition.get = shouldCache
 ? createComputedGetter(key)
 : userDef
 sharedPropertyDefinition.set = noop
 } else {
 // 當計算屬性是對象時,設置計算屬性的getter和setter
 sharedPropertyDefinition.get = userDef.get
 ? shouldCache && userDef.cache !== false
 ? createComputedGetter(key)
 : userDef.get
 : noop
 sharedPropertyDefinition.set = userDef.set
 ? userDef.set
 : noop
 }
 // 非生產環境下,如果沒喲定義計算屬性的setter
 // 想設置計算屬性時給出警告
 if (process.env.NODE_ENV !== 'production' &&
 sharedPropertyDefinition.set === noop) {
 sharedPropertyDefinition.set = function () {
 warn(
 `Computed property "${key}" was assigned to but it has no setter.`,
 this
 )
 }
 }
 // 以重新設置的屬性描述符為基礎在實例對象上定義計算屬性
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 定義createComputedGetter,創建計算屬性getter
// 目的是在非服務器渲染情況下建立計算屬性的觀察依賴,
// 并根據其依賴屬性返回計算后的值
function createComputedGetter (key) {
 return function computedGetter () {
 const watcher = this._computedWatchers && this._computedWatchers[key]
 if (watcher) {
 watcher.depend()
 return watcher.evaluate()
 }
 }
}

計算屬性的初始化相對復雜一些,首先要對計算屬性建立觀察,然后再在實例上重新定義計算屬性,并且執行屬性代理。由于加入了服務器渲染的功能,在定義計算屬性的時候對使用環境做判斷,是非服務器渲染會影響到計算屬性的定義,這是由于服務器渲染下使用框架時,計算屬性是不提供 setter 的;另外也要根據用戶定義的值是函數或者對象來對計算屬性重新定義 getter 和 setter。從這段代碼里可以看出一個非常重要的程序,即在獲取計算屬性的時候才去計算它的值,這正是懶加載的實現。

initMethods

// 定義initMethods方法,接受實例vm,配置屬性methods
function initMethods (vm: Component, methods: Object) {
 // 獲取實例的props
 const props = vm.$options.props
 // 遍歷methods對象
 for (const key in methods) {
 // 非生產環境下給出警告
 if (process.env.NODE_ENV !== 'production') {
 // 未賦值方法警告
 if (methods[key] == null) {
 warn(
 `Method "${key}" has an undefined value in the component definition. ` +
 `Did you reference the function correctly?`,
 vm
 )
 }
 // 與props屬性名沖突警告
 if (props && hasOwn(props, key)) {
 warn(
 `Method "${key}" has already been defined as a prop.`,
 vm
 )
 }
 // 與保留字沖突警告
 if ((key in vm) && isReserved(key)) {
 warn(
 `Method "${key}" conflicts with an existing Vue instance method. ` +
 `Avoid defining component methods that start with _ or $.`
 )
 }
 }
 // 在實例上定義方法,賦值為用戶未定義函數或空函數
 vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
 }
}

initMethods 函數非常簡單,除了一大段在非生產環境里報告檢查沖突的代碼,唯一的內容就是在實例上定義相應的方法并且把上下文綁定到實例對象上,這樣即便不是使用箭頭函數,在方法內也默認用 this 指代了實例對象。

initWatch

// 定義initWatch函數,接受實例vm和配置屬性watch
function initWatch (vm: Component, watch: Object) {
 // 遍歷watch
 for (const key in watch) {
 // 暫存屬性的值
 const handler = watch[key]
 // 如果handler是數組
 if (Array.isArray(handler)) {
 // 遍歷數組為每一個元素創建相應watcher
 for (let i = 0; i < handler.length; i++) {
 createWatcher(vm, key, handler[i])
 }
 } else {
 // 竇否則handler應該是函數,直接為key創建watcher
 createWatcher(vm, key, handler)
 }
 }
}

// 定義createWatcher函數
// 接受實例vm、表達式或函數expOrFn,處理器handler,可選的options
function createWatcher (
 vm: Component,
 expOrFn: string | Function,
 handler: any,
 options?: Object
) {
 // 如果handler是對象
 if (isPlainObject(handler)) {
 // 將handler賦值給options.
 options = handler
 // 重新賦值handler
 handler = handler.handler
 }
 // 如果handler是字符串,在實例上尋找handler并賦值給handler
 if (typeof handler === 'string') {
 handler = vm[handler]
 }
 // 創建觀察并返回
 return vm.$watch(expOrFn, handler, options)
}

initWatcher 為傳入的觀察對象創建監視器,比較簡單。值得注意的是參數的傳入類型,觀察對象 expOrFn 可以有兩種方式,一種是字符串,一種是函數,在 Watcher 類中對此參數進行了檢測,而在初始化的函數里不對它做任何處理。handler 對象也可以接受對象或字符串類型,在代碼中對這兩種傳入方式做判斷,最終找到handler引用的函數傳入 $watch。

stateMixin

探索完了 initState 函數之后,繼續來看看 state 混入的方法 stateMixin,在這個函數里會提供上面還未曾提到的 $watch 方法的具體實現:

// 定義并導出stateMixin函數,接收參數Vue
export function stateMixin (Vue: Class<Component>) {
 // 使用 Object.defineProperty 方法直接聲明定義對象時,flow會發生問題
 // 所以必須在此程序化定義對象
 // flow somehow has problems with directly declared definition object
 // when using Object.defineProperty, so we have to procedurally build up
 // the object here.
 // 定義dataDef對象
 const dataDef = {}
 // 定義dataDef的get方法,返回Vue實例私有屬性_data
 dataDef.get = function () { return this._data }
 // 定義propsDef對象
 const propsDef = {}
 // 定義propsDef的get方法,返回Vue實例私有屬性_props
 propsDef.get = function () { return this._props }
 // 非生產環境下,定義dataDef和propsDef的set方法
 if (process.env.NODE_ENV !== 'production') {
 // dataDef的set方法接收Object類型的newData形參
 dataDef.set = function (newData: Object) {
 // 提示避免傳入對象覆蓋屬性$data
 // 推薦使用嵌套的數據屬性代替
 warn(
 'Avoid replacing instance root $data. ' +
 'Use nested data properties instead.',
 this
 )
 }
 // 設置propsDef的set方法為只讀
 propsDef.set = function () {
 warn(`$props is readonly.`, this)
 }
 }
 // 定義Vue原型對象公共屬性$data,并賦值為dataDef
 Object.defineProperty(Vue.prototype, '$data', dataDef)
 // 定義Vue原型對象公共屬性$props,并賦值為propsDef
 Object.defineProperty(Vue.prototype, '$props', propsDef)

 // 定義Vue原型對象的$set方法,并賦值為從觀察者導入的set函數
 Vue.prototype.$set = set
 // 定義Vue原型對象的$delete方法,并賦值為從觀察者導入的del函數
 Vue.prototype.$delete = del

 // 定義Vue原型對象的$watch方法
 // 接收字符串或函數類型的expOrFn,從命名中可看出希望為表達式或函數
 // 接收任何類型的cb,這里希望為回調函數或者是一個對象
 // 接收對象類型的options
 // 要求返回函數類型
 Vue.prototype.$watch = function (
 expOrFn: string | Function,
 cb: any,
 options?: Object
 ): Function {
 // 把實例賦值給vm變量,類型需為Component
 const vm: Component = this
 // 如果cb是純粹的對象類型
 if (isPlainObject(cb)) {
 // 返回createWatcher函數
 return createWatcher(vm, expOrFn, cb, options)
 }
 // 定義觀察目標的options,大多數情況下為undefined
 options = options || {}
 // 定義options的user屬性值為true,標識為用戶定義
 options.user = true
 // 創建watcher實例
 const watcher = new Watcher(vm, expOrFn, cb, options)
 // 如果options的immediate為真
 if (options.immediate) {
 // 在vm上調用cb回調函數,并傳入watcher.value作為參數
 cb.call(vm, watcher.value)
 }
 // 返回unwatchFn函數
 return function unwatchFn () {
 // 執行watcher.teardown()方法清除觀察
 watcher.teardown()
 }
 }
}

stateMixin執行的是關于狀態觀察的一系列方法的混入,主要是三個方面:

  • 定義實例 $data 和 $props 屬性的存取器
  • 定義實例的 $set、$delete 方法,具體實在定義在觀察者模塊中
  • 定義實例的 $watch 方法
  • 到這里,關于狀態初始化的部分就探索完畢了,接下來要繼續研究另一個與開發過程緊密關聯的部分——虛擬節點和模板渲染。

    狀態初始化是與我們在開發的時候最息息相關的部分,在創建實例對象的配置對象中,我們設置了這些屬性和方法,實例初始化的過程中對這些傳入的配置進行了很多預先的處理,這就是狀態初始化背后的邏輯。在探索到這一部分的時候才真正的感到,終于與平時的開發關聯起來了。

    聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    Vue源碼探究之狀態初始化

    Vue源碼探究之狀態初始化:繼續隨著核心類的初始化展開探索其他的模塊,這一篇來研究一下Vue的狀態初始化。這里的狀態初始化指的就是在創建實例的時候,在配置對象里定義的屬性、數據變量、方法等是如何進行初始處理的。由于隨后的數據更新變動都交給觀察系統來負責,所以在事先弄明白
    推薦度:
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 精品国产乱码一区二区三区麻豆 | 国产在线一区二区三区欧美 | 九九久久国产 | 亚洲第一页中文字幕 | 日本高清天码一区在线播放 | 亚洲第一区在线观看 | 成人精品一区二区www | 国内精品1区1区3区4区 | 在线日韩视频 | 日韩精品123 | 午夜一区二区三区 | 国内视频一区二区三区 | 欧美瑟图 | 成人免费视频一区二区 | 一级久久 | 亚洲一区二区三区在线免费观看 | 96精品在线 | 欧美激情在线播放一区二区三区 | 无毛片| 91精品国产色综合久久 | 四虎影视最新地址 | 久久91av| 九九热精品在线观看 | 图片区 日韩 欧美 亚洲 | 日韩综合在线 | 日韩成人免费在线视频 | 亚洲欧美日韩中文字幕在线 | 欧美我不卡 | 人人爽人人草 | 日韩欧美精品 | 亚洲欧美一区二区三区久久 | 国产成人黄网址在线视频 | 国产成人综合久久精品红 | 在线观看精品一区 | 日韩电影免费在线观看中文字幕 | 国产午夜视频在线观看 | 午夜视频在线免费观看 | 国产成人综合一区精品 | 九九久久精品国产 | 亚洲欧洲日韩综合 | 一区二区三区成人 |