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

Vue 2 模版編譯流程詳解

開發 前端
到此我們應該了解了 vue 是如何打包構建將模板編譯為渲染函數的,有了渲染函數后,只需要將渲染函數的 this 指向組件實例,即可和組件的響應式數據綁定。

圖片圖片

vue 中有這樣一張響應式系統的流程圖,vue 會將模板語法編譯成 render 函數,通過 render 函數渲染生成 Virtual dom,但是官方并沒有對模板編譯有詳細的介紹,這篇文章帶大家一起學習下 vue 的模板編譯。

為了更好理解 vue 的模板編譯這里我整理了一份模板編譯的整體流程,如下所示,下面將用源碼解讀的方式來找到模板編譯中的幾個核心步驟,進行詳細說明:

圖片圖片

1、起步

這里我使用 webpack 來打包 vue 文件,來分析 vue 在模板編譯中的具體流程,如下所示,下面是搭建的項目結構和文件內容:

項目結構

├─package-lock.json
├─package.json
├─src
|  ├─App.vue
|  └index.js
├─dist
|  └main.js
├─config
|   └webpack.config.js

App.vue

<template>
  <div id="box">
    {{ count }}
  </div>
</template>

<script>
export default {
  props: {},
  data() {
    return {
      count: 0
    }
  }
}
</script>

<style scoped>
#box {
  background: red;
}
</style>

webpack.config.js

const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      // 它會應用到普通的 `.js` 文件
      // 以及 `.vue` 文件中的 `<script>` 塊
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      // 它會應用到普通的 `.css` 文件
      // 以及 `.vue` 文件中的 `<style>` 塊
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}

如上 webpack.config.js 所示,webpack 可以通過 vue-loader 識別 vue 文件,vue-loader 是 webpack 用來解析 .vue 文件的 loader,主要作用是將單文件組件(SFC),解析成為 webpack 可識別的 JavaScript 模塊。

打包構建

搭建好整個目錄項目后,執行 npm run build ,會將 vue 文件解析打包成對應的 bundle,并輸出至 dist 目錄下,下面是打包后的產出,對應 App.vue 的產物:

/***/ "./src/App.vue"

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
  /* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__) \n/* harmony export */
});

var _App_vue_vue_type_template_id_7ba5bd90_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true&");

var _App_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__( "./src/App.vue?vue&type=script&lang=js&");



var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__("./node_modules/vue-loader/lib/runtime/componentNormalizer.js");

var component = (0, _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(
  _App_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__["default"],
  _App_vue_vue_type_template_id_7ba5bd90_scoped_true___WEBPACK_IMPORTED_MODULE_0__.render, _App_vue_vue_type_template_id_7ba5bd90_scoped_true___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns, false, null, "7ba5bd90", null,/* hot reload */
)

從上方的產物可以看出,App.vue 文件被編譯分為三塊,_App_vue_vue_type_template_id_7ba5bd90_scoped_true___WEBPACK_IMPORTED_MODULE_0__  _App_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1___node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__,這三個模塊恰好對應vue模板中的 templatescriptstyle這三個標簽的模板內容,所以得出結論:vue-loader 會將 vue 模板中的templatescriptstyle 標簽內容分解為三個模塊。為此,我找到 vue-loader 的源碼,下面分析其源碼邏輯:

vue-loader 源碼

源碼里很清楚的可以看到 vue-loader 使用了 vue/compiler-sfc 中的 parse 方法對 vue 的源文件進行的解析,將模板語法解析為一段可描述的對象

module.exports = function (source) {
  // 這里就是.vue文件的AST
  const loaderContext = this

    ...
  // 解析.vue原文件,source對應的就是.vue模板
  const descriptor = compiler.parse({
    source,
    compiler: options.compiler || templateCompiler,
    filename,
    sourceRoot,
    needMap: sourceMap
  })

    ...

  // 使用webpack query source
  let templateImport = `var render, staticRenderFns`
  let templateRequest
  if (descriptor.template) {
    const src = descriptor.template.src || resourcePath
    const idQuery = `&id=${id}`
    const scopedQuery = hasScoped ? `&scoped=true` : ``
    const attrsQuery = attrsToQuery(descriptor.template.attrs)
    // const tsQuery =
    // options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
    const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
    const request = (templateRequest = stringifyRequest(src + query))
    templateImport = `import { render, staticRenderFns } from ${request}`
  }

    ...

  code += `\nexport default component.exports`
  return code
}

 descriptor 進行打印,輸出結果如下,vue-loader 對源文件編譯后,vue 模板會被轉化成抽象語法樹(AST),此處便是模板編譯的入口,使用編譯后的 AST 將 vue 模板拆分為 template 、script 和 style 三部分,方便后面 webpack 通過 resourceQuery 匹配分發到各個loader 進行二次解析編譯,template 部分會被 template-loader 進行二次編譯解析,最終生成render 函數。

{
  source: '<template>\n' +
    '  <div id="box">\n' +
    '    {{ count }}\n' +
    '  </div>\n' +
    '</template>\n' +
    '\n' +
    '<script>\n' +
    'export default {\n' +
    '  props: {},\n' +
    '  data() {\n' +
    '    return {\n' +
    '      count: 0\n' +
    '    }\n' +
    '  }\n' +
    '}\n' +
    '</script>\n' +
    '\n' +
    '<style>\n' +
    '#box {\n' +
    '  background: red;\n' +
    '}\n' +
    '</style>\n',
  filename: 'App.vue',
  template: {
    type: 'template',
    content: '\n<div id="box">\n  {{ count }}\n</div>\n',
    start: 10,
    end: 53,
    attrs: {}
  },
  script: {
    type: 'script',
    content: '\n' +
      'export default {\n' +
      '  props: {},\n' +
      '  data() {\n' +
      '    return {\n' +
      '      count: 0\n' +
      '    }\n' +
      '  }\n' +
      '}\n',
    start: 74,
    end: 156,
    attrs: {}
  },
  ....
}

template-loader

template-loader 的作用是將 import { render, staticRenderFns } from "./App.vue?vue&type=template&id=7ba5bd90&" 模塊編譯成 render 函數并導出,以下是編譯產物:

// 編譯前
<div id="box">
  {{ count }}
</div>

// 編譯后
var render = function render() {
  var _vm = this,
    _c = _vm._self._c
  return _c("div", { attrs: { id: "box" } }, [
    _vm._v("\n  " + _vm._s(_vm.count) + "\n"),
  ])
}
var staticRenderFns = []
render._withStripped = true

export { render, staticRenderFns }

template-loader 核心原理是通過 vue/compiler-sfc 將模板轉換成為 render 函數,并返回 template 編譯產物

module.exports = function (source) {
  const loaderContext = this
    ...
  // 接收模板編譯核心庫
  const { compiler, templateCompiler } = resolveCompiler(ctx, loaderContext)

    ...

  // 開啟編譯
  const compiled = compiler.compileTemplate(finalOptions)

    ...

  // 編譯后產出,code就是render函數
  const { code } = compiled

  // 導出template模塊
  return code + `\nexport { render, staticRenderFns }`
}

2、模板編譯流程

vue/compiler-sfc 是模板編譯的核心庫,在 vue2.7 版本中使用,而 vue2.7 以下的版本都是使用vue-template-compiler,本質兩個包的功能是一樣的,都可以將模板語法編譯為 JavaScript,接下來我們來解析一下在模板編譯過程中使用的方法:

parseHTML 階段

可以將 vue 文件中的模板語法轉義為 AST,為后續創建 dom 結構做預處理

export function parseHTML(html, options: HTMLParserOptions) {
  // 存儲解析后的標簽
  const stack: any[] = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  let index = 0
  let last, lastTag
  // 循環 html 字符串結構
  while (html) {
    // 記錄當前最新html
    last = html
    if (!lastTag || !isPlainTextElement(lastTag)) {
      // 獲取以 < 為開始的位置
      let textEnd = html.indexOf('<')
      if (textEnd === 0) {
        // 解析注釋
        if (comment.test(html)) {
          const commentEnd = html.indexOf('-->')

          if (commentEnd >= 0) {
            if (options.shouldKeepComment && options.comment) {
              options.comment(
                html.substring(4, commentEnd),
                index,
                index + commentEnd + 3
              )
            }
            advance(commentEnd + 3)
            continue
          }
        }

        // 解析條件注釋
        if (conditionalComment.test(html)) {
          const conditionalEnd = html.indexOf(']>')

          if (conditionalEnd >= 0) {
            advance(conditionalEnd + 2)
            continue
          }
        }

        // 解析 Doctype
        const doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }

        // 解析截取結束標簽
        const endTagMatch = html.match(endTag)
        if (endTagMatch) {
          const curIndex = index
          advance(endTagMatch[0].length)
          parseEndTag(endTagMatch[1], curIndex, index)
          continue
        }

        // 解析截取開始標簽
        const startTagMatch = parseStartTag()
        if (startTagMatch) {
          handleStartTag(startTagMatch)
          if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
            advance(1)
          }
          continue
        }
      }

      let text, rest, next
      if (textEnd >= 0) {
        rest = html.slice(textEnd)
        while (
          !endTag.test(rest) &&
          !startTagOpen.test(rest) &&
          !comment.test(rest) &&
          !conditionalComment.test(rest)
        ) {
          // < in plain text, be forgiving and treat it as text
          next = rest.indexOf('<', 1)
          if (next < 0) break
          textEnd += next
          rest = html.slice(textEnd)
        }
        text = html.substring(0, textEnd)
      }

      // 純文本節點
      if (textEnd < 0) {
        text = html
      }

      // 截取文本節點
      if (text) {
        advance(text.length)
      }

      if (options.chars && text) {
        options.chars(text, index - text.length, index)
      }
    } else {
      let endTagLength = 0
      const stackedTag = lastTag.toLowerCase()
      const reStackedTag =
        reCache[stackedTag] ||
        (reCache[stackedTag] = new RegExp(
          '([\\s\\S]*?)(</' + stackedTag + '[^>]*>)',
          'i'
        ))
      const rest = html.replace(reStackedTag, function (all, text, endTag) {
        endTagLength = endTag.length
        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
          text = text
            .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
            .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')
        }
        if (shouldIgnoreFirstNewline(stackedTag, text)) {
          text = text.slice(1)
        }
        if (options.chars) {
          options.chars(text)
        }
        return ''
      })
      index += html.length - rest.length
      html = rest
      parseEndTag(stackedTag, index - endTagLength, index)
    }

    if (html === last) {
      options.chars && options.chars(html)
      break
    }
  }

  // 清空閉合標簽
  parseEndTag()

  // 截取標簽,前后推進位置
  function advance(n) {
    index += n
    html = html.substring(n)
  }

  // 解析開始標簽
  function parseStartTag() {
    const start = html.match(startTagOpen)
    if (start) {
      const match: any = {
        tagName: start[1],
        attrs: [],
        start: index
      }
      advance(start[0].length)
      let end, attr
      while (
        !(end = html.match(startTagClose)) &&
        (attr = html.match(dynamicArgAttribute) || html.match(attribute))
      ) {
        attr.start = index
        advance(attr[0].length)
        attr.end = index
        match.attrs.push(attr)
      }
      if (end) {
        match.unarySlash = end[1]
        advance(end[0].length)
        match.end = index
        return match
      }
    }
  }

  // 匹配處理開始標簽
  function handleStartTag(match) {
    const tagName = match.tagName
    const unarySlash = match.unarySlash

    if (expectHTML) {
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag)
      }
      if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
        parseEndTag(tagName)
      }
    }

    const unary = isUnaryTag(tagName) || !!unarySlash

    const l = match.attrs.length
    const attrs: ASTAttr[] = new Array(l)
    for (let i = 0; i < l; i++) {
      const args = match.attrs[i]
      const value = args[3] || args[4] || args[5] || ''
      const shouldDecodeNewlines =
        tagName === 'a' && args[1] === 'href'
          ? options.shouldDecodeNewlinesForHref
          : options.shouldDecodeNewlines
      attrs[i] = {
        name: args[1],
        value: decodeAttr(value, shouldDecodeNewlines)
      }
      if (__DEV__ && options.outputSourceRange) {
        attrs[i].start = args.start + args[0].match(/^\s*/).length
        attrs[i].end = args.end
      }
    }

    if (!unary) {
      stack.push({
        tag: tagName,
        lowerCasedTag: tagName.toLowerCase(),
        attrs: attrs,
        start: match.start,
        end: match.end
      })
      lastTag = tagName
    }

    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

  // 解析結束標簽
  function parseEndTag(tagName?: any, start?: any, end?: any) {
    let pos, lowerCasedTagName
    if (start == null) start = index
    if (end == null) end = index

    // Find the closest opened tag of the same type
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase()
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break
        }
      }
    } else {
      // If no tag name is provided, clean shop
      pos = 0
    }

    if (pos >= 0) {
      // Close all the open elements, up the stack
      for (let i = stack.length - 1; i >= pos; i--) {
        if (__DEV__ && (i > pos || !tagName) && options.warn) {
          options.warn(`tag <${stack[i].tag}> has no matching end tag.`, {
            start: stack[i].start,
            end: stack[i].end
          })
        }
        if (options.end) {
          options.end(stack[i].tag, start, end)
        }
      }

      // Remove the open elements from the stack
      stack.length = pos
      lastTag = pos && stack[pos - 1].tag
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end)
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end)
      }
      if (options.end) {
        options.end(tagName, start, end)
      }
    }
  }
}

genElement 階段

genElement 會將 AST 預發轉義為字符串代碼,后續可將其包裝成 render 函數的返回值

// 將AST預發轉義成render函數字符串
export function genElement(el: ASTElement, state: CodegenState): string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }
  if (el.staticRoot && !el.staticProcessed) {
      // 輸出靜態樹
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
      // 處理v-once指令
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
      // 處理循環結構
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
      // 處理條件語法
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
      // 處理子標簽
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
      // 處理插槽
    return genSlot(el, state)
  } else {
    // 處理組件和dom元素
       ...
    return code
  }
}

通過genElement函數包裝處理后,將vue 模板的 template 標簽部分轉換為 render 函數,如下所示:

const compiled = compiler.compileTemplate({
  source: '\n' +
    '<div id="box">\n' +
    '  {{ count }}\n' +
    '  <button @add="handleAdd">+</button>\n' +
    '</div>\n'
});

const { code } = compiled;

// 編譯后
var render = function render() {
  var _vm = this,
    _c = _vm._self._c
  return _c("div", { attrs: { id: "box" } }, [
    _vm._v("\n  " + _vm._s(_vm.count) + "\n  "),
    _c("button", { on: { add: _vm.handleAdd } }, [_vm._v("+")]),
  ])
}
var staticRenderFns = []
render._withStripped = true

compilerToFunction 階段

 genElement 階段編譯的字符串產物,通過 new Function將 code 轉為函數

export function createCompileToFunctionFn(compile: Function): Function {
  const cache = Object.create(null)
  return function compileToFunctions(
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    ...
    // 編譯
    const compiled = compile(template, options)

    // 將genElement階段的產物轉化為function
    function createFunction(code, errors) {
      try {
        return new Function(code)
      } catch (err: any) {
        errors.push({ err, code })
        return noop
      }
    }

    const res: any = {}
    const fnGenErrors: any[] = []
    // 將code轉化為function
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })
    ...
  }
}

為了方便理解,使用斷點調試,來看一下 compileTemplate 都經歷了哪些操作:

首先會判斷是否需要預處理,如果需要預處理,則會對 template 模板進行預處理并返回處理結果,此處跳過預處理,直接進入 actuallCompile 函數

這里可以看到本身內部還有一層編譯函數對 template 進行編譯,這才是最核心的編譯方法,而這個 compile 方法來源于 createCompilerCreator

圖片圖片

createCompilerCreator 返回了兩層函數,最終返回值則是 compile 和 compileToFunction,這兩個是將 template 轉為 render 函數的關鍵,可以看到 template 會被解析成 AST 樹,最后通過 generate 方法轉義成函數 code,接下來我們看一下parse函數中是如何將 template 轉為 AST 的。

圖片圖片

繼續向下 debug 后,會走到 parseHTML 函數,這個函數是模板編譯中用來解析 HTML 結構的核心方法,通過回調 + 遞歸最終遍歷整個 HTML 結構并將其轉化為 AST 樹。

parseHTML 階段

使用 parseHTML 解析成的 AST 創建 render 函數和 Vdom

圖片圖片

genElement 階段

將 AST 結構解析成為虛擬 dom 樹

圖片圖片

最終編譯輸出為 render 函數,得到最終打包構建的產物。

圖片圖片

3、總結

到此我們應該了解了 vue 是如何打包構建將模板編譯為渲染函數的,有了渲染函數后,只需要將渲染函數的 this 指向組件實例,即可和組件的響應式數據綁定。vue 的每一個組件都會對應一個渲染 Watcher ,他的本質作用是把響應式數據作為依賴收集,當響應式數據發生變化時,會觸發 setter 執行響應式依賴通知渲染 Watcher 重新執行 render 函數做到頁面數據的更新。

參考文獻

vue 2 官方文檔 ( https://v2.cn.vuejs.org/ )

責任編輯:武曉燕 來源: 政采云技術
相關推薦

2022-08-31 08:09:35

Vue2AST模版

2021-10-22 06:53:44

Apk編譯打包

2011-01-19 17:13:44

Sylpheed

2021-11-01 17:31:21

Camera2 相機開發

2020-09-14 08:56:30

Vue模板

2009-06-11 13:22:18

JBPM數據庫

2010-10-20 13:43:37

C++編譯器

2015-12-30 10:29:40

Git協作流程詳解

2021-12-02 10:05:01

鴻蒙HarmonyOS應用

2009-11-30 16:38:30

Android

2022-05-18 07:58:21

Linux程序編譯代碼

2024-07-04 12:10:50

2024-12-16 08:10:00

Spring開發

2011-08-16 10:17:12

XCode模版引擎XTemplate

2021-06-28 09:38:50

鴻蒙HarmonyOS應用

2010-05-11 19:27:56

MySQL內核

2015-07-13 15:52:18

反編譯Android APK

2009-12-30 09:22:01

Ubuntu編譯

2023-03-09 11:02:40

linux編譯源碼

2009-08-04 18:10:35

ASP.NET動態編譯
點贊
收藏

51CTO技術棧公眾號

日韩欧美黄色影院| 国产.欧美.日韩| 日韩精品在线视频美女| 337p粉嫩大胆噜噜噜鲁| 日韩av高清在线| 日韩精品亚洲一区| 日韩有码视频在线| 国内精品免费视频| 成人va天堂| 日韩一区日韩二区| 久久国产精品99久久久久久丝袜| 在线视频一区二区三区四区| 久久精品国产www456c0m| 日韩午夜激情av| 成人在线免费观看av| 尤物网在线观看| 99精品久久99久久久久| 国产日本欧美一区二区三区在线 | 久久精品久久久久| 你懂的在线观看网站| 成人全视频免费观看在线看| 亚洲综合免费观看高清在线观看 | 欧美精品激情| 亚洲美女激情视频| 亚洲精品久久久久久| 欧美动物xxx| 亚洲一区国产视频| 手机看片福利永久国产日韩| 人妻少妇精品无码专区| 久久国产精品色| 97成人精品区在线播放| 日本高清不卡免费| 久久av中文| 亚洲成人aaa| 亚洲精品中文字幕乱码无线| 日韩免费福利视频| 亚洲国产美女搞黄色| 一区二区三区精品国产| 久热av在线| www.欧美色图| 成人蜜桃视频| 国产精品女同一区二区| 青娱乐精品视频在线| 午夜精品一区二区三区av| 久久久久久久久久久久久女过产乱| 亚洲制服欧美另类| 日韩成人在线电影网| 欧美做受高潮中文字幕| 日韩中文字幕无砖| 91精品欧美一区二区三区综合在| 日本在线一二三区| 日韩精品一区二区三区av| 欧美日韩国产中文精品字幕自在自线| www.欧美黄色| 97人人在线| 中文字幕av一区二区三区高| 日韩精品无码一区二区三区| 国产在线免费观看| 久久精品男人天堂av| 欧美精品尤物在线| 青春有你2免费观看完整版在线播放高清 | 久久人人97超碰精品888| 一区二区三区影视| 亚洲91视频| 久久成年人视频| 欧美日韩在线国产| 国产精品啊啊啊| 久久久免费观看视频| 久草精品视频在线观看| 国产亚洲欧洲| 国产成人综合精品| 中文文字幕一区二区三三| 青娱乐精品视频| 成人性生交大片免费看视频直播| 97久久人国产精品婷婷| 国产一区二区三区精品视频| 91精品婷婷国产综合久久蝌蚪| 精品国产18久久久久久| 粉嫩av一区二区三区粉嫩| 精品国产福利| 第一页在线观看| 中文字幕亚洲一区二区va在线| 久久精品国产精品亚洲精品色| 大片免费在线看视频| 亚洲午夜精品一区二区三区他趣| 奇米影视亚洲色图| 婷婷午夜社区一区| 欧美喷潮久久久xxxxx| 原创真实夫妻啪啪av| 成人免费在线电影网| 精品视频www| 黄色裸体一级片| 欧美日本一区二区视频在线观看 | 日韩精品第二页| 日韩视频免费观看高清完整版| 911亚洲精选| 免费毛片在线不卡| 久久久极品av| www成人在线| 久久精品国产第一区二区三区| av激情久久| 国产精品视频一区二区久久| 亚洲精品伦理在线| 美女福利视频在线| 国产成人免费av一区二区午夜| 精品成人佐山爱一区二区| 人妻大战黑人白浆狂泄| 欧美另类综合| 国产精品福利观看| 亚洲欧美激情国产综合久久久| 久久免费看少妇高潮| 中文精品一区二区三区| 松下纱荣子在线观看| 51精品久久久久久久蜜臀| 欧美黑人欧美精品刺激| 亚洲a一区二区三区| 欧美最顶级丰满的aⅴ艳星| 国产片高清在线观看| ww久久中文字幕| 少妇高潮大叫好爽喷水| 日本欧美日韩| 日韩大陆毛片av| 日本妇女毛茸茸| 免费观看30秒视频久久| 久久国产精品高清| 国产美女情趣调教h一区二区| 欧美三级中文字幕| 中出视频在线观看| 国产精品sm| 成人有码视频在线播放| 国产黄色在线| 日韩欧美综合在线视频| 性色av蜜臀av浪潮av老女人| 91精品成人| 国产欧美一区二区三区在线看| 蜜桃视频在线免费| 午夜国产精品一区| 少妇搡bbbb搡bbb搡打电话| 91精品国产乱码久久久久久| 国产精品美女免费看| 每日更新在线观看av| 亚洲一区二区视频在线观看| 中文字幕55页| 亚洲自拍偷拍网| 国产美女久久精品| 99中文字幕一区| 日本高清成人免费播放| 黄瓜视频污在线观看| 亚洲少妇一区| 久久福利电影| 中文字幕影音在线| 亚洲女同性videos| 久久久久久久久久久影院| 国产99久久久国产精品潘金| 中文字幕人妻熟女人妻洋洋| 亚洲2区在线| 欧美精品videossex88| 性猛交富婆╳xxx乱大交天津| 一区二区三区欧美| 91精品国产高清91久久久久久| 在线一区免费| 成人午夜影院在线观看| 暧暧视频在线免费观看| 亚洲成人国产精品| 成年人免费高清视频| 久久视频一区二区| 日本爱爱免费视频| 四虎成人精品永久免费av九九| 国产美女主播一区| 99在线播放| 亚洲国产天堂久久综合网| 青青操免费在线视频| 91在线云播放| 五月婷婷之综合激情| 999久久久免费精品国产| 亚洲一区二区三区乱码aⅴ蜜桃女| 黄色网在线免费看| 亚洲成人精品视频在线观看| 一级片视频在线观看| 国产嫩草影院久久久久| 国产高清av片| 99视频一区| 色就是色欧美| 91丨精品丨国产| 午夜精品福利在线观看| 免费在线观看污视频| 3d动漫精品啪啪1区2区免费| 国产午夜久久久| 欧美国产激情一区二区三区蜜月| 久久综合在线观看| 国产农村妇女精品一区二区| 免费观看成人高| 美女100%一区| 久久91亚洲精品中文字幕| 色吊丝在线永久观看最新版本| 欧美色综合网站| 久久精品国产亚洲av高清色欲| 91麻豆国产香蕉久久精品| 亚洲欧美视频二区| 影音先锋亚洲一区| 无码免费一区二区三区免费播放| 亚洲高清999| 日本一区二区在线播放| а天堂中文在线官网| 日韩精品亚洲视频| 国产视频一二三四区| 色欲综合视频天天天| 欧美精品成人久久| 久久久久久久久免费| 特种兵之深入敌后| 三级不卡在线观看| 日本中文字幕亚洲| 91一区二区| 久久精品ww人人做人人爽| 亚洲精品大全| 国产成人一区二区三区| 欧美草逼视频| 久久亚洲精品毛片| 成年网站在线| 亚洲精品国精品久久99热| 涩涩视频在线观看| 欧美性20hd另类| 久草中文在线视频| 亚洲人成精品久久久久| av网在线播放| 91蜜桃视频在线| 波多野结衣办公室双飞 | 国产精品视频久久久久| 男人天堂视频在线观看| 美女av一区二区三区| 91精彩在线视频| 亚洲片在线资源| 四虎在线观看| 亚洲国内高清视频| 亚洲国产成人一区二区| 337p亚洲精品色噜噜噜| 伊人免费在线观看高清版| 一本久久a久久免费精品不卡| 久久久久久久久久一区二区三区| 18成人在线视频| 国产真人真事毛片视频| 国产欧美综合色| 久久精品国产亚洲av久| 久久综合av免费| 欧美精品黑人猛交高潮| 99riav久久精品riav| 日本一区二区在线观看视频| 国产盗摄一区二区三区| 交换做爰国语对白| 国产麻豆精品95视频| 一级日本黄色片| 国产一区二区精品久久99| 搡的我好爽在线观看免费视频| 久久99精品久久只有精品| 激情黄色小视频| 久久99日本精品| 岛国av免费在线| 国产一区二区三区香蕉| 少妇性l交大片7724com| 高清在线成人网| 大尺度在线观看| aaa亚洲精品| 亚洲av片不卡无码久久| 久久久久久久网| 天堂av免费在线| 一区二区三区四区中文字幕| 免费在线看黄网址| 亚洲第一在线综合网站| 成人在线免费看视频| 在线观看亚洲一区| 亚洲中文字幕在线观看| 日韩视频一区在线观看| 乱精品一区字幕二区| 亚洲精品日韩丝袜精品| 高清av电影在线观看| 久久视频在线视频| 久草在线新免费首页资源站| 欧洲精品在线视频| 欧美精品免费观看二区| 国产精品99久久久久久董美香 | 欧美裸身视频免费观看| 国产丝袜精品丝袜| 日本一区二区不卡| 24小时成人在线视频| 成人午夜电影免费在线观看| 中文字幕中文字幕精品| 一本一道久久a久久综合精品| 伊人色**天天综合婷婷| 欧美视频在线观看网站| 久久这里只有| 日韩欧美中文视频| 91麻豆swag| 国产67194| 天天操天天色综合| 一区二区美女视频| 亚洲精品国精品久久99热一| 91吃瓜网在线观看| 久久久久国产视频| 成人在线视频免费| 国产精品一区二区不卡视频| 日本不卡高清| 久久精品国产sm调教网站演员| 日本在线播放一区二区三区| 国产成人精品一区二区三区在线观看| 久久综合五月天婷婷伊人| 四虎884aa成人精品| 欧美性xxxxxxx| 精品国产九九九| 国产香蕉精品视频一区二区三区| 在线观看av免费| 国产精品久久久久7777婷婷| 777久久精品| 一本色道久久99精品综合| 亚洲精品1区2区| 亚洲在线观看网站| 久久久国际精品| 日韩女优在线观看| 欧美一级xxx| 尤物网址在线观看| 青草青草久热精品视频在线网站 | 久草视频免费在线播放| 精品视频在线看| 极品美乳网红视频免费在线观看| 九九久久国产精品| 日韩精品一级毛片在线播放| 欧美精品欧美精品| 亚洲综合精品四区| 美女久久久久久久久| 亚洲精品欧美专区| 中文字幕人成人乱码亚洲电影| 精品视频在线播放| yellow在线观看网址| 超碰97国产在线| 伊人色**天天综合婷婷| 小泽玛利亚视频在线观看| www激情久久| 天堂网一区二区三区| 精品久久国产字幕高潮| 亚洲91av| 成人动漫在线视频| 欧美三级特黄| 亚洲乱妇老熟女爽到高潮的片 | 黑人巨茎大战欧美白妇| 久久精品99国产精品日本| 成人一级片免费看| 欧美日韩一区在线观看| 在线免费观看黄色| 国产欧美久久久久久| 日韩欧美视频在线播放| 孩娇小videos精品| 国产精品国产精品国产专区不蜜 | 精品久久免费视频| 欧美精品一区二区久久久| 日本在线视频网址| 国产精品jizz视频| 最新成人av网站| 视频二区在线播放| 中文字幕一区在线观看视频| 国产精品女同一区二区| 日韩亚洲欧美中文高清在线| 超碰在线资源| 免费99视频| 欧美一级一区| 色天使在线视频| 欧美日韩国产精品一区二区不卡中文| 亚洲精品一级片| 91av在线播放视频| 亚洲精品蜜桃乱晃| 北条麻妃在线视频观看| 久久久精品tv| 一区二区自拍偷拍| 欧美www在线| 国产成人高清精品免费5388| 中国丰满人妻videoshd| 久久综合av免费| 91久久精品无码一区二区| 久久伊人色综合| 国产专区精品| 日本香蕉视频在线观看| 国产福利91精品| 综合激情网五月| 色老头一区二区三区在线观看| 久久伦理中文字幕| www.国产在线视频| 欧美韩日一区二区三区四区| 一区二区三区精彩视频| 国内精品400部情侣激情| 日韩中文av| 8x8x成人免费视频| 亚洲一区二区四区蜜桃| 深夜视频在线免费| 国产精品国产自产拍高清av水多| 欧美禁忌电影网| 亚洲涩涩在线观看| 亚洲一区二区在线观看视频| 国产精品久久久久久久龚玥菲| 成人做爽爽免费视频| 激情婷婷久久| 亚洲精品国产精品国自产网站| 91精品国产综合久久蜜臀|