• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題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關(guān)鍵字專題關(guān)鍵字專題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
    當(dāng)前位置: 首頁 - 科技 - 知識(shí)百科 - 正文

    聊聊Vue.js的template編譯的問題

    來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 22:28:16
    文檔

    聊聊Vue.js的template編譯的問題

    聊聊Vue.js的template編譯的問題:寫在前面 因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學(xué)習(xí)過程中,為Vue加上了中文的注釋https://g
    推薦度:
    導(dǎo)讀聊聊Vue.js的template編譯的問題:寫在前面 因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學(xué)習(xí)過程中,為Vue加上了中文的注釋https://g

    寫在前面

    因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。

    文章的原地址:https://github.com/answershuto/learnVue。

    在學(xué)習(xí)過程中,為Vue加上了中文的注釋https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以對(duì)其他想學(xué)習(xí)Vue源碼的小伙伴有所幫助。

    可能會(huì)有理解存在偏差的地方,歡迎提issue指出,共同學(xué)習(xí),共同進(jìn)步。

    $mount

    首先看一下mount的代碼

    /*把原本不帶編譯的$mount方法保存下來,在最后會(huì)調(diào)用。*/
    const mount = Vue.prototype.$mount
    /*掛載組件,帶模板編譯*/
    Vue.prototype.$mount = function (
     el?: string | Element,
     hydrating?: boolean
    ): Component {
     el = el && query(el)
    
     /* istanbul ignore if */
     if (el === document.body || el === document.documentElement) {
     process.env.NODE_ENV !== 'production' && warn(
     `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
     )
     return this
     }
    
     const options = this.$options
     // resolve template/el and convert to render function
     /*處理模板templete,編譯成render函數(shù),render不存在的時(shí)候才會(huì)編譯template,否則優(yōu)先使用render*/
     if (!options.render) {
     let template = options.template
     /*template存在的時(shí)候取template,不存在的時(shí)候取el的outerHTML*/
     if (template) {
     /*當(dāng)template是字符串的時(shí)候*/
     if (typeof template === 'string') {
     if (template.charAt(0) === '#') {
     template = idToTemplate(template)
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== 'production' && !template) {
     warn(
     `Template element not found or is empty: ${options.template}`,
     this
     )
     }
     }
     } else if (template.nodeType) {
     /*當(dāng)template為DOM節(jié)點(diǎn)的時(shí)候*/
     template = template.innerHTML
     } else {
     /*報(bào)錯(cuò)*/
     if (process.env.NODE_ENV !== 'production') {
     warn('invalid template option:' + template, this)
     }
     return this
     }
     } else if (el) {
     /*獲取element的outerHTML*/
     template = getOuterHTML(el)
     }
     if (template) {
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     mark('compile')
     }
    
     /*將template編譯成render函數(shù),這里會(huì)有render以及staticRenderFns兩個(gè)返回,這是vue的編譯時(shí)優(yōu)化,static靜態(tài)不需要在VNode更新時(shí)進(jìn)行patch,優(yōu)化性能*/
     const { render, staticRenderFns } = compileToFunctions(template, {
     shouldDecodeNewlines,
     delimiters: options.delimiters
     }, this)
     options.render = render
     options.staticRenderFns = staticRenderFns
    
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     mark('compile end')
     measure(`${this._name} compile`, 'compile', 'compile end')
     }
     }
     }
     /*Github:https://github.com/answershuto*/
     /*調(diào)用const mount = Vue.prototype.$mount保存下來的不帶編譯的mount*/
     return mount.call(this, el, hydrating)
    }
    
    

    通過mount代碼我們可以看到,在mount的過程中,如果render函數(shù)不存在(render函數(shù)存在會(huì)優(yōu)先使用render)會(huì)將template進(jìn)行compileToFunctions得到render以及staticRenderFns。譬如說手寫組件時(shí)加入了template的情況都會(huì)在運(yùn)行時(shí)進(jìn)行編譯。而render function在運(yùn)行后會(huì)返回VNode節(jié)點(diǎn),供頁面的渲染以及在update的時(shí)候patch。接下來我們來看一下template是如何編譯的。

    一些基礎(chǔ)

    首先,template會(huì)被編譯成AST語法樹,那么AST是什么?

    在計(jì)算機(jī)科學(xué)中,抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax tree),是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式,這里特指編程語言的源代碼。

    AST會(huì)經(jīng)過generate得到render函數(shù),render的返回值是VNode,VNode是Vue的虛擬DOM節(jié)點(diǎn),具體定義如下:

    export default class VNode {
     tag: string | void;
     data: VNodeData | void;
     children: ?Array<VNode>;
     text: string | void;
     elm: Node | void;
     ns: string | void;
     context: Component | void; // rendered in this component's scope
     functionalContext: Component | void; // only for functional component root nodes
     key: string | number | void;
     componentOptions: VNodeComponentOptions | void;
     componentInstance: Component | void; // component instance
     parent: VNode | void; // component placeholder node
     raw: boolean; // contains raw HTML? (server only)
     isStatic: boolean; // hoisted static node
     isRootInsert: boolean; // necessary for enter transition check
     isComment: boolean; // empty comment placeholder?
     isCloned: boolean; // is a cloned node?
     isOnce: boolean; // is a v-once node?
     /*Github:https://github.com/answershuto*/
     
     constructor (
     tag?: string,
     data?: VNodeData,
     children?: ?Array<VNode>,
     text?: string,
     elm?: Node,
     context?: Component,
     componentOptions?: VNodeComponentOptions
     ) {
     /*當(dāng)前節(jié)點(diǎn)的標(biāo)簽名*/
     this.tag = tag
     /*當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的對(duì)象,包含了具體的一些數(shù)據(jù)信息,是一個(gè)VNodeData類型,可以參考VNodeData類型中的數(shù)據(jù)信息*/
     this.data = data
     /*當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),是一個(gè)數(shù)組*/
     this.children = children
     /*當(dāng)前節(jié)點(diǎn)的文本*/
     this.text = text
     /*當(dāng)前虛擬節(jié)點(diǎn)對(duì)應(yīng)的真實(shí)dom節(jié)點(diǎn)*/
     this.elm = elm
     /*當(dāng)前節(jié)點(diǎn)的名字空間*/
     this.ns = undefined
     /*編譯作用域*/
     this.context = context
     /*函數(shù)化組件作用域*/
     this.functionalContext = undefined
     /*節(jié)點(diǎn)的key屬性,被當(dāng)作節(jié)點(diǎn)的標(biāo)志,用以優(yōu)化*/
     this.key = data && data.key
     /*組件的option選項(xiàng)*/
     this.componentOptions = componentOptions
     /*當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的組件的實(shí)例*/
     this.componentInstance = undefined
     /*當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)*/
     this.parent = undefined
     /*簡而言之就是是否為原生HTML或只是普通文本,innerHTML的時(shí)候?yàn)閠rue,textContent的時(shí)候?yàn)閒alse*/
     this.raw = false
     /*靜態(tài)節(jié)點(diǎn)標(biāo)志*/
     this.isStatic = false
     /*是否作為跟節(jié)點(diǎn)插入*/
     this.isRootInsert = true
     /*是否為注釋節(jié)點(diǎn)*/
     this.isComment = false
     /*是否為克隆節(jié)點(diǎn)*/
     this.isCloned = false
     /*是否有v-once指令*/
     this.isOnce = false
     }
    
     // DEPRECATED: alias for componentInstance for backwards compat.
     /* istanbul ignore next */
     get child (): Component | void {
     return this.componentInstance
     }
    }
    
    

    關(guān)于VNode的一些細(xì)節(jié),請(qǐng)參考VNode節(jié)點(diǎn)。

    createCompiler

    createCompiler用以創(chuàng)建編譯器,返回值是compile以及compileToFunctions。compile是一個(gè)編譯器,它會(huì)將傳入的template轉(zhuǎn)換成對(duì)應(yīng)的AST樹、render函數(shù)以及staticRenderFns函數(shù)。而compileToFunctions則是帶緩存的編譯器,同時(shí)staticRenderFns以及render函數(shù)會(huì)被轉(zhuǎn)換成Funtion對(duì)象。

    因?yàn)椴煌脚_(tái)有一些不同的options,所以createCompiler會(huì)根據(jù)平臺(tái)區(qū)分傳入一個(gè)baseOptions,會(huì)與compile本身傳入的options合并得到最終的finalOptions。

    compileToFunctions

    首先還是貼一下compileToFunctions的代碼。

     /*帶緩存的編譯器,同時(shí)staticRenderFns以及render函數(shù)會(huì)被轉(zhuǎn)換成Funtion對(duì)象*/
     function compileToFunctions (
     template: string,
     options?: CompilerOptions,
     vm?: Component
     ): CompiledFunctionResult {
     options = options || {}
    
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== 'production') {
     // detect possible CSP restriction
     try {
     new Function('return 1')
     } catch (e) {
     if (e.toString().match(/unsafe-eval|CSP/)) {
     warn(
     'It seems you are using the standalone build of Vue.js in an ' +
     'environment with Content Security Policy that prohibits unsafe-eval. ' +
     'The template compiler cannot work in this environment. Consider ' +
     'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
     'templates into render functions.'
     )
     }
     }
     }
     /*Github:https://github.com/answershuto*/
     // check cache
     /*有緩存的時(shí)候直接取出緩存中的結(jié)果即可*/
     const key = options.delimiters
     ? String(options.delimiters) + template
     : template
     if (functionCompileCache[key]) {
     return functionCompileCache[key]
     }
    
     // compile
     /*編譯*/
     const compiled = compile(template, options)
    
     // check compilation errors/tips
     if (process.env.NODE_ENV !== 'production') {
     if (compiled.errors && compiled.errors.length) {
     warn(
     `Error compiling template:\n\n${template}\n\n` +
     compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
     vm
     )
     }
     if (compiled.tips && compiled.tips.length) {
     compiled.tips.forEach(msg => tip(msg, vm))
     }
     }
    
     // turn code into functions
     const res = {}
     const fnGenErrors = []
     /*將render轉(zhuǎn)換成Funtion對(duì)象*/
     res.render = makeFunction(compiled.render, fnGenErrors)
     /*將staticRenderFns全部轉(zhuǎn)化成Funtion對(duì)象 */
     const l = compiled.staticRenderFns.length
     res.staticRenderFns = new Array(l)
     for (let i = 0; i < l; i++) {
     res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)
     }
    
     // check function generation errors.
     // this should only happen if there is a bug in the compiler itself.
     // mostly for codegen development use
     /* istanbul ignore if */
     if (process.env.NODE_ENV !== 'production') {
     if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
     warn(
     `Failed to generate render function:\n\n` +
     fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n$[code]\n`).join('\n'),
     vm
     )
     }
     }
    
     /*存放在緩存中,以免每次都重新編譯*/
     return (functionCompileCache[key] = res) 
     }
    
    

    我們可以發(fā)現(xiàn),在閉包中,會(huì)有一個(gè)functionCompileCache對(duì)象作為緩存器。

     /*作為緩存,防止每次都重新編譯*/
     const functionCompileCache: {
     [key: string]: CompiledFunctionResult;
     } = Object.create(null)

    在進(jìn)入compileToFunctions以后,會(huì)先檢查緩存中是否有已經(jīng)編譯好的結(jié)果,如果有結(jié)果則直接從緩存中讀取。這樣做防止每次同樣的模板都要進(jìn)行重復(fù)的編譯工作。

     // check cache
     /*有緩存的時(shí)候直接取出緩存中的結(jié)果即可*/
     const key = options.delimiters
     ? String(options.delimiters) + template
     : template
     if (functionCompileCache[key]) {
     return functionCompileCache[key]
     }
    

    在compileToFunctions的末尾會(huì)將編譯結(jié)果進(jìn)行緩存

     /*存放在緩存中,以免每次都重新編譯*/
     return (functionCompileCache[key] = res)

     compile

     /*編譯,將模板template編譯成AST樹、render函數(shù)以及staticRenderFns函數(shù)*/
     function compile (
     template: string,
     options?: CompilerOptions
     ): CompiledResult {
     const finalOptions = Object.create(baseOptions)
     const errors = []
     const tips = []
     finalOptions.warn = (msg, tip) => {
     (tip ? tips : errors).push(msg)
     }
    
     /*做下面這些merge的目的因?yàn)椴煌脚_(tái)可以提供自己本身平臺(tái)的一個(gè)baseOptions,內(nèi)部封裝了平臺(tái)自己的實(shí)現(xiàn),然后把共同的部分抽離開來放在這層compiler中,所以在這里需要merge一下*/
     if (options) {
     // merge custom modules
     /*合并modules*/
     if (options.modules) {
     finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
     }
     // merge custom directives
     if (options.directives) {
     /*合并directives*/
     finalOptions.directives = extend(
     Object.create(baseOptions.directives),
     options.directives
     )
     }
     // copy other options
     for (const key in options) {
     /*合并其余的options,modules與directives已經(jīng)在上面做了特殊處理了*/
     if (key !== 'modules' && key !== 'directives') {
     finalOptions[key] = options[key]
     }
     }
     }
    
     /*基礎(chǔ)模板編譯,得到編譯結(jié)果*/
     const compiled = baseCompile(template, finalOptions)
     if (process.env.NODE_ENV !== 'production') {
     errors.push.apply(errors, detectErrors(compiled.ast))
     }
     compiled.errors = errors
     compiled.tips = tips
     return compiled
     }
    
    

    compile主要做了兩件事,一件是合并option(前面說的將平臺(tái)自有的option與傳入的option進(jìn)行合并),另一件是baseCompile,進(jìn)行模板template的編譯。

    來看一下baseCompile

    baseCompile

    function baseCompile (
     template: string,
     options: CompilerOptions
    ): CompiledResult {
     /*parse解析得到ast樹*/
     const ast = parse(template.trim(), options)
     /*
     將AST樹進(jìn)行優(yōu)化
     優(yōu)化的目標(biāo):生成模板AST樹,檢測(cè)不需要進(jìn)行DOM改變的靜態(tài)子樹。
     一旦檢測(cè)到這些靜態(tài)樹,我們就能做以下這些事情:
     1.把它們變成常數(shù),這樣我們就再也不需要每次重新渲染時(shí)創(chuàng)建新的節(jié)點(diǎn)了。
     2.在patch的過程中直接跳過。
     */
     optimize(ast, options)
     /*根據(jù)ast樹生成所需的code(內(nèi)部包含render與staticRenderFns)*/
     const code = generate(ast, options)
     return {
     ast,
     render: code.render,
     staticRenderFns: code.staticRenderFns
     }
    }
    

    baseCompile首先會(huì)將模板template進(jìn)行parse得到一個(gè)AST語法樹,再通過optimize做一些優(yōu)化,最后通過generate得到render以及staticRenderFns。

    parse

    parse的源碼可以參見https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53。

    parse會(huì)用正則等方式解析template模板中的指令、class、style等數(shù)據(jù),形成AST語法樹。

    optimize

    optimize的主要作用是標(biāo)記static靜態(tài)節(jié)點(diǎn),這是Vue在編譯過程中的一處優(yōu)化,后面當(dāng)update更新界面時(shí),會(huì)有一個(gè)patch的過程,diff算法會(huì)直接跳過靜態(tài)節(jié)點(diǎn),從而減少了比較的過程,優(yōu)化了patch的性能。

    generate

    generate是將AST語法樹轉(zhuǎn)化成render funtion字符串的過程,得到結(jié)果是render的字符串以及staticRenderFns字符串。

    至此,我們的template模板已經(jīng)被轉(zhuǎn)化成了我們所需的AST語法樹、render function字符串以及staticRenderFns字符串。

    舉個(gè)例子

    來看一下這段代碼的編譯結(jié)果

    <div class="main" :class="bindClass">
     <div>{{text}}</div>
     <div>hello world</div>
     <div v-for="(item, index) in arr">
     <p>{{item.name}}</p>
     <p>{{item.value}}</p>
     <p>{{index}}</p>
     <p>---</p>
     </div>
     <div v-if="text">
     {{text}}
     </div>
     <div v-else></div>
    </div>
    

    轉(zhuǎn)化后得到AST樹,如下圖:


    我們可以看到最外層的div是這顆AST樹的根節(jié)點(diǎn),節(jié)點(diǎn)上有許多數(shù)據(jù)代表這個(gè)節(jié)點(diǎn)的形態(tài),比如static表示是否是靜態(tài)節(jié)點(diǎn),staticClass表示靜態(tài)class屬性(非bind:class)。children代表該節(jié)點(diǎn)的子節(jié)點(diǎn),可以看到children是一個(gè)長度為4的數(shù)組,里面包含的是該節(jié)點(diǎn)下的四個(gè)div子節(jié)點(diǎn)。children里面的節(jié)點(diǎn)與父節(jié)點(diǎn)的結(jié)構(gòu)類似,層層往下形成一棵AST語法樹。

    再來看看由AST得到的render函數(shù)

    with(this){
     return _c( 'div',
     {
     /*static class*/
     staticClass:"main",
     /*bind class*/
     class:bindClass
     },
     [
     _c( 'div', [_v(_s(text))]),
     _c('div',[_v("hello world")]),
     /*這是一個(gè)v-for循環(huán)*/
     _l(
     (arr),
     function(item,index){
     return _c( 'div',
     [_c('p',[_v(_s(item.name))]),
     _c('p',[_v(_s(item.value))]),
     _c('p',[_v(_s(index))]),
     _c('p',[_v("---")])]
     )
     }
     ),
     /*這是v-if*/
     (text)?_c('div',[_v(_s(text))]):_c('div',[_v("no text")])],
     2
     )
    }
    

    _c,_v,_s,_q

    看了render function字符串,發(fā)現(xiàn)有大量的_c,_v,_s,_q,這些函數(shù)究竟是什么?

    帶著問題,我們來看一下core/instance/render。

    /*處理v-once的渲染函數(shù)*/
     Vue.prototype._o = markOnce
     /*將字符串轉(zhuǎn)化為數(shù)字,如果轉(zhuǎn)換失敗會(huì)返回原字符串*/
     Vue.prototype._n = toNumber
     /*將val轉(zhuǎn)化成字符串*/
     Vue.prototype._s = toString
     /*處理v-for列表渲染*/
     Vue.prototype._l = renderList
     /*處理slot的渲染*/
     Vue.prototype._t = renderSlot
     /*檢測(cè)兩個(gè)變量是否相等*/
     Vue.prototype._q = looseEqual
     /*檢測(cè)arr數(shù)組中是否包含與val變量相等的項(xiàng)*/
     Vue.prototype._i = looseIndexOf
     /*處理static樹的渲染*/
     Vue.prototype._m = renderStatic
     /*處理filters*/
     Vue.prototype._f = resolveFilter
     /*從config配置中檢查eventKeyCode是否存在*/
     Vue.prototype._k = checkKeyCodes
     /*合并v-bind指令到VNode中*/
     Vue.prototype._b = bindObjectProps
     /*創(chuàng)建一個(gè)文本節(jié)點(diǎn)*/
     Vue.prototype._v = createTextVNode
     /*創(chuàng)建一個(gè)空VNode節(jié)點(diǎn)*/
     Vue.prototype._e = createEmptyVNode
     /*處理ScopedSlots*/
     Vue.prototype._u = resolveScopedSlots
    
     /*創(chuàng)建VNode節(jié)點(diǎn)*/
     vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
    
    

    通過這些函數(shù),render函數(shù)最后會(huì)返回一個(gè)VNode節(jié)點(diǎn),在_update的時(shí)候,經(jīng)過patch與之前的VNode節(jié)點(diǎn)進(jìn)行比較,得出差異后將這些差異渲染到真實(shí)的DOM上。

    聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    聊聊Vue.js的template編譯的問題

    聊聊Vue.js的template編譯的問題:寫在前面 因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學(xué)習(xí)過程中,為Vue加上了中文的注釋https://g
    推薦度:
    標(biāo)簽: VUE 聊聊 編譯
    • 熱門焦點(diǎn)

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 一色屋精品视频在线观看| 在线观看国产精品普通话对白精品| 国产精品嫩草影院一二三区| 亚洲精品无码专区久久久| 精品国产第1页| 人人妻人人澡人人爽精品日本| 久久996热精品xxxx| 91精品国产综合久久香蕉| 99久久久国产精品免费无卡顿| 亚洲国产美女精品久久久久∴| 久久久久久青草大香综合精品 | 91麻豆精品国产| 亚洲AV永久无码精品一百度影院| 精品人妻伦一二三区久久| 日韩精品在线一区二区| 成人区人妻精品一区二区不卡视频| 丰满人妻熟妇乱又伦精品劲| 国产精品高清一区二区三区| 中文字幕无码精品亚洲资源网久久 | 97久久精品无码一区二区天美| 亚洲第一区精品日韩在线播放| 色播精品免费小视频| 凹凸国产熟女精品视频app| 国产欧美精品一区二区三区四区 | 久久久久亚洲精品天堂久久久久久 | 人妻精品久久久久中文字幕69| 久久亚洲精品无码播放| 91精品国产色综久久| 精品国产欧美另类一区| 无码日韩精品一区二区三区免费| 欧美日韩国产精品| 久久精品国产一区二区三区不卡| 91精品国产成人网在线观看 | 国内精品视频在线观看| 国产成人无码精品久久久免费 | 久久国产成人亚洲精品影院 | 午夜精品久久影院蜜桃| 精品97国产免费人成视频| 青青草原综合久久大伊人精品| 国产AV国片精品| 国内精品九九久久久精品|