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

前端插件式可擴展架構設計心得

開發 前端
講到這里你可能以為我要開始講過度設計這個主題了,但其實不然,我只是想以這個話題作為引子,和大家討論一下關于設計一個插件架構我是如何思考的。

 引子

大家可能不知道,鄙人之前人送外號“過度設計”。作為一個自信的研發人員,我總是希望我開發的系統可以解決之后所有的問題,用一套抽象可以覆蓋之后所有的擴展場景。當然最終往往能夠證明我的愚昧與思慮不足。先知曾說過“當一個東西什么都可以做時,他往往什么都做不了”。過度的抽象,過度的開放性,往往讓接觸他的人無所適從。講到這里你可能以為我要開始講過度設計這個主題了,但其實不然,我只是想以這個話題作為引子,和大家討論一下關于設計一個插件架構我是如何思考的。

[[414359]]

為什么需要插件

我們的軟件系統往往是要面向持續性的迭代的,在開發之初很難把所有需要支持的功能都想清楚,有時候還需要借助社區的力量去持續生產新的功能點,或者優化已有的功能。這就需要我們的軟件系統具備一定的可擴展性。插件模式就是我們常常選用的方法。事實上,現存的大量軟件系統或工具都是使用插件方式來實現可擴展性的。比如大家最熟悉的小可愛——VSCode,其插件擁有量已經超越了他的前輩 Atom,發布到市場中的數量目前是 24894 個。這些插件幫助我們定制編輯器的外觀或行為,增加額外功能,支持更多語法類型,大大提升了開發效率,同時也不斷拓展著自身的用戶群體。又或者是我們熟知的瀏覽器 Chrome,其核心競爭力之一也是豐富的插件市場,使其不論是對開發者還是普通使用者都已成為了不可獲取的一個工具。另外還有 Webpack、Nginx 等等各種工具,這邊就不一一贅述了。根據目前各個系統的插件設計,總結下來,我們創造插件主要是幫助我們解決以下兩種類型的問題:

  •  為系統提供全新的能力
  •  對系統現有能力進行定制

同時,在解決上面這類問題的時候做到:

  •  插件代碼與系統代碼在工程上解耦,可以獨立開發,并對開發者隔離框架內部邏輯的復雜度
  •  可動態化引入與配置

并且進一步地可以實現:

  •  通過對多個單一職責的插件進行組合,可以實現多種復雜邏輯,實現邏輯在復雜場景中的復用

這里提到的不管是提供新能力,還是進行能力定制,都既可以針對系統開發者本身,也可以針對三方開發者。結合上面的特征,我們嘗試簡單描述一下插件是什么吧。插件一般是可獨立完成某個或一系列功能的模塊。一個插件是否引入一定不會影響系統原本的正常運行(除非他和另一個插件存在依賴關系)。插件在運行時被引入系統,由系統控制調度。一個系統可以存在復數個插件,這些插件可通過系統預定的方式進行組合。

怎么實現插件模式

插件模式本質是一種設計思想,并沒有一個一成不變或者是萬金油的實現。但我們經過長期的代碼實踐,其實已經可以總結出一套方法論來指導插件體系的實現,并且其中的一些實現細節是存在社區認可度比較高的“最佳實踐”的。本文在攥寫過程中也參考研讀了社區比較有名的一些項目的插件模式設計,包括但不僅限于 Koa、Webpack、Babel 等。

1. 解決問題前首先要定義問題

實現一套插件模式的第一步,永遠都是先定義出你需要插件化來幫助你解決的問題是什么。這往往是具體問題具體分析的,并總是需要你對當前系統的能力做一定程度的抽象。比如 Babel,他的核心功能是將一種語言的代碼轉化為另一種語言的代碼,他面臨的問題就是,他無法在設計時就窮舉語法類型,也不了解應該如何去轉換一種新的語法,因此需要提供相應的擴展方式。為此,他將自己的整體流程抽象成了 parse、transform、generate 三個步驟,并主要面向 parse 和 transform 提供了插件方式做擴展性支持。在 parse 這層,他核心要解決的問題是怎么去做分詞,怎么去做詞義語法的理解。在 transform 這層要做的則是,針對特定的語法樹結構,應該如何轉換成已知的語法樹結構。很明顯,babel 他很清楚地定義了 parse 和 transform 兩層的插件要完成的事情。當然也有人可能會說,為什么我一定要定義清楚問題呢,插件體系本來就是為未來的不確定性服務的。這樣的說法對,也不對。計算機程序永遠是面向確定性的,我們需要有明確的輸入格式,明確的輸出格式,明確的可以依賴的能力。解決問題一定是在已知的一個框架內的。這就引出了定義問題的一門藝術——如何賦予不確定以確定性,在不確定中尋找確定。說人話,就是“抽象”,這也是為什么最開始我會以過度設計作為引子。我在進行問題定義的時候,最常使用的是樣本分析法,這種方法并非捷徑,但總歸是有點效的。樣本分析法,就是先著眼于整理已知待解決的問題,將這些問題作為樣本嘗試分類和提取共性,從而形成一套抽象模式。然后再通過一些不確定但可能未來待解決的問題來測試,是否存在無法套用的情況。光說無用,下面我們還是以 babel 來舉個栗子,當然 babel 的抽象設計其實本質就是有理論支撐的,在有現有理論已經為你做好抽象時,還是盡量用現成的就好啦。

Babel 主要解決的問題是把新語法的代碼在不改變邏輯的情況下如何轉換成舊語法的代碼,簡單來說就是 code => code 的一個問題。但是需要轉什么,怎么轉,這些是會隨著語法規范不斷更新變化的,因此需要使用插件模式來提升其未來可拓展性。我們當下要解決的問題也許是如何轉換 es6 新語法的內容,以及 JSX 這種框架定制的 DSL。我們當然可以簡單地串聯一系列的正則處理,但是你會發現每一個插件都會有大量重復的識別分析類邏輯,不但加大了運行開銷,同時也很難避免互相影響導致的問題。Babel 選擇了把解析與轉換兩個動作拆開來,分別使用插件來實現。解析的插件要解決的問題是如何解析代碼,把 Code 轉化為 AST。這個問題對于不同的語言又可以拆解為相同的兩個事情,如何分詞,以及如何做詞義解析。當然詞義解析還能是如何構筑上下文、如何產出 AST 節點等等,就不再細分了。最終形成的就是下圖這樣的模式,插件專注解決這幾個細分問題。轉換這邊的,則可分為如何查找固定 AST 節點,以及如何轉換,最終形成了 Visitor 模式,這里就不再詳細說了。那么我們再思考一下,如果未來 ES7、8、9(相對于設計場景的未來)等新語法出爐時,是不是依然可以使用這樣的模式去解決問題呢?看起來是可行的。

這就是前面所說的在不確定中尋找確定性,盡可能減少系統本身所面臨的不確定,通過拆解問題去限定問題。那么定義清楚問題,我們大概就完成了 1/3 的工作了,下面就是要正式開始思考如何設計了。

2. 插件架構設計繞不開的幾大要素

插件模式的設計,可以簡單也可以復雜,我們不能指望一套插件模式適合所有的場景,如果真的可以的話,我也不用寫這篇文章了,給大家甩一個 npm 地址就完事了。這也是為什么在設計之前我們一定要先定義清楚問題。具體選擇什么方式實現,一定是根據具體解決的問題權衡得出的。不過呢,這事終歸還是有跡可循,有法可依的。當正式開始設計我們的插件架構時,我們所要思考的問題往往離不開以下幾點。整個設計過程其實就是為每一點選擇合適的方案,最后形成一套插件體系。這幾點分別是:

  •  如何注入、配置、初始化插件
  •  插件如何影響系統
  •  插件輸入輸出的含義與可以使用的能力
  •  復數個插件之間的關系是怎么樣的

下面就針對每個點詳細解釋一下

如何注入、配置、初始化插件

注入、配置、初始化其實是幾個分開的事情。但都同屬于 Before 的事情,所以就放在一起講了。先來講一講注入,其實本質上就是如何讓系統感知到插件的存在。注入的方式一般可以分為 聲明式 和 編程式。聲明式就是通過配置信息,告訴系統應該去哪里去取什么插件,系統運行時會按照約定與配置去加載對應的插件。類似 Babel,可以通過在配置文件中填寫插件名稱,運行時就會去 modules 目錄下去查找對應的插件并加載。編程式的就是系統提供某種注冊 API,開發者通過將插件傳入 API 中來完成注冊。兩種對比的話,聲明式主要適合自己單獨啟動不用接入另一個軟件系統的場景,這種情況一般使用編程式進行定制的話成本會比較高,但是相對的,對于插件命名和發布渠道都會有一些限制。編程式則適合于需要在開發中被引入一個外部系統的情況。當然也可以兩種方式都進行支持。然后是插件配置,配置的主要目的是實現插件的可定制,因為一個插件在不同使用場景下,可能對于其行為需要做一些微調,這時候如果每個場景都去做一個單獨的插件那就有點小題大作了。配置信息一般在注入時一起傳入,很少會支持注入后再進行重新配置。配置如何生效其實也和插件初始化的有點關聯,初始化這事可以分為方式和時機兩個細節來講,我們先講講方式。常見的方式我大概列舉兩種。一種是工廠模式,一個插件暴露出來的是一個工廠函數,由調用者或者插件架構來將提供配置信息傳入,生成插件實例。另一種是運行時傳入,插件架構在調度插件時會通過約定的上下文把配置信息給到插件。工廠模式咱們繼續拿 babel 來舉例吧。 

  1. function declare<  
  2.     O extends Record<string, any> 
  3.     R extends babelbabel.PluginObj = babel.PluginObj  
  4. >
  5.     builder: (api: BabelAPI, options: O, dirname: string) => R,  
  6. ): (api: object, options: O | null | undefined, dirname: string) => R; 

上面代碼中的 builder 呢就是我們說到的工廠函數了,他最終將產出一個 Plugin 實例。builder 通過 options 獲取到配置信息,并且這里設計上還支持通過 api 設置一些運行環境信息,不過這并不是必須的,所以不細說了。簡化一下就是: 

  1. type TPluginFactory<OPTIONS, PLUGIN> = (options: OPTIONS) => PLUGIN; 

所以初始化呢,自然也可以是通過調用工廠函數初始化、初始化完成后再注入、不需要初始化三種。一般我們不選擇初始化完成后再注入,因為解耦的訴求,我們盡量在插件中只做聲明。是否使用工廠模式則看插件是否需要初始化這一步驟。大部分情況下,如果你決定不好,還是推薦優先選擇工廠模式,可以應對后面更多復雜場景。初始化的時機也可以分為注入即初始化、統一初始化、運行時才初始化。很多情況下 注入即初始化、統一初始化 可以結合使用,具體的區分我嘗試通過一張表格來對應說明:

另外還有個問題也在這里提一下,在一些系統中,我們可能依賴許多插件組合來完成一件復雜的事情,為了屏蔽單獨引入并配置插件的復雜性,我們還會提供一種 Preset 的概念,去打包多個插件及其配置。使用者只需要引入 Preset 即可,不用關心里面有哪些插件。例如 Babel 在支持 react 語法時,其實要引入 syntax-jsx transform-react-jsx transform-react-display-name transform-react-pure-annotationsd 等多個插件,最終給到的是 preset-react這樣一個包。

插件如何影響系統

插件對系統的影響我們可以總結為三方面:行為、交互、展示。單獨一個插件可能只涉及其中一點。根據具體場景,有些方面也不必去影響,比如一個邏輯引擎類型的系統,就大概率不需要展示這塊的東西啦。VSCode 插件大致覆蓋了這三個,所以我們可以拿一個簡單的插件來看下。這里我們選擇了 Clock in status bar 這個插件,這個插件的功能很簡單,就是在狀態欄加一個時鐘,或者你可以在編輯內容內快速插入當前時間。

整個項目里最主要的是下面這些內容:

在 package.json 中,通過擴展的 contributes 字段為插件注冊了一個命令,和一個配置菜單。 

  1. "main": "./extension", // 入口文件地址  
  2. "contributes": {  
  3.   "commands": [{  
  4.     "command": "clock.insertDateTime",  
  5.     "title": "Clock: Insert date and time"  
  6.   }],  
  7.   "configuration": {  
  8.     "type": "object",  
  9.     "title": "Clock configuration",  
  10.     "properties": {  
  11.       "clock.dateFormat": {  
  12.         "type": "string",  
  13.         "default": "hh:MM TT",  
  14.         "description": "Clock: Date format according to https://github.com/felixge/node-dateformat"  
  15.       }  
  16.     }  
  17.   }  
  18. }, 

在入口文件 extension.js 中則通過系統暴露的 API 創建了狀態欄的 UI,并注冊了命令的具體行為。 

  1. 'use strict';  
  2. // The module 'vscode' contains the VS Code extensibility API  
  3. // Import the module and reference it with the alias vscode in your code below  
  4. const  
  5.   clockService = require('./clockservice'),  
  6.   ClockStatusBarItem = require('./clockstatusbaritem'), 
  7.   vscode = require('vscode');  
  8. // this method is called when your extension is activated  
  9. // your extension is activated the very first time the command is executed  
  10. function activate(context) {  
  11.   // Use the console to output diagnostic information (console.log) and errors (console.error)  
  12.   // This line of code will only be executed once when your extension is activated  
  13.   // The command has been defined in the package.json file  
  14.   // Now provide the implementation of the command with  registerCommand  
  15.   // The commandId parameter must match the command field in package.json  
  16.   context.subscriptions.push(new ClockStatusBarItem());  
  17.   context.subscriptions.push(vscode.commands.registerTextEditorCommand('clock.insertDateTime', (textEditor, edit) => {  
  18.     textEditor.selections.forEach(selection => {  
  19.       const  
  20.         start = selection.start,  
  21.         end = selection.end;  
  22.       if (start.line === end.line && start.character === end.character) {  
  23.         edit.insert(start, clockService());  
  24.       } else {  
  25.         edit.replace(selection, clockService());  
  26.       }  
  27.     });  
  28.   }));  
  29.  
  30. exports.activate = activate;  
  31. // this method is called when your extension is deactivated  
  32. function deactivate() {  
  33.  
  34. exports.deactivate = deactivate; 

上述這個例子有點大塊兒,有點稍顯粗糙。那么總結下來我們看一下,在最開始我們提到的三個方面分別是如何體現的。

  •  UI:我們通過系統 API 創建了一個狀態欄組件。我們通過配置信息構建了一個 配置頁。
  •  交互:我們通過注冊命令,增加了一項指令交互。
  •  邏輯:我們新增了一項插入當前時間的能力邏輯。

所以我們在設計一個插件架構時呢,也主要就從這三方面是否會被影響考慮即可。那么插件又怎么去影響系統呢,這個過程的前提是插件與系統間建立一份契約,約定好對接的方式。這份契約可以包含文件結構、配置格式、API 簽名。還是結合 VSCode 的例子來看看:

  •  文件結構:沿用了 NPM 的傳統,約定了目錄下 package.json 承載元信息。
  •  配置格式:約定了 main 的配置路徑作為代碼入口,私有字段 contributes 聲明命令與配置。
  •  API 簽名:約定了擴展必須提供 activate 和 deactivate 兩個接口。并提供了 vscode 下各項 API 來完成注冊。

UI 和 交互的定制邏輯,本質上依賴系統本身的實現方式。這里重點講一下一般通過哪些模式,去調用插件中的邏輯。

直接調用

這個模式很直白,就是在系統的自身邏輯中,根據需要去調用注冊的插件中約定的 API,有時候插件本身就只是一個 API。比如上面例子中的 activate 和 deactivate 兩個接口。這種模式很常見,但調用處可能會關注比較多的插件處理相關邏輯。

鉤子機制(事件機制)

系統定義一系列事件,插件將自己的邏輯掛載在事件監聽上,系統通過觸發事件進行調度。上面例子中的 clock.insertDateTime 命令也可以算是這類,是一個命令觸發事件。在這個機制上,webpack 是一個比較明顯的例子,我們來看一個簡單的 webpack 插件: 

  1. // 一個 JavaScript 命名函數。  
  2. function MyExampleWebpackPlugin() {  
  3. };  
  4. // 在插件函數的 prototype 上定義一個 `apply` 方法。  
  5. MyExampleWebpackPlugin.prototype.apply = function(compiler) {  
  6.   // 指定一個掛載到 webpack 自身的事件鉤子。  
  7.   compiler.plugin('webpacksEventHook', function(compilation /* 處理 webpack 內部實例的特定數據。*/, callback) {  
  8.     console.log("This is an example plugin!!!");  
  9.     // 功能完成后調用 webpack 提供的回調。  
  10.     callback();  
  11.   });  
  12. }; 

這里的插件就將“在 console 打印 This is an example plugin!!!”這一行為注冊到了 webpacksEventHook 這個鉤子上,每當這個鉤子被觸發時,會調用一次這個邏輯。這種模式比較常見,webpack 也專門做了一份封裝服務這個模式,https://github.com/webpack/tapable[1]。通過定義了多種不同調度邏輯的鉤子,你可以在任何系統中植入這款模式,并能滿足你不同的調度需求(調度模式我們在下一部分中詳細講述)。 

  1. const {  
  2.     SyncHook,  
  3.     SyncBailHook,  
  4.     SyncWaterfallHook,  
  5.     SyncLoopHook,  
  6.     AsyncParallelHook,  
  7.     AsyncParallelBailHook,  
  8.     AsyncSeriesHook,  
  9.     AsyncSeriesBailHook,  
  10.     AsyncSeriesWaterfallHook  
  11. } = require("tapable"); 

鉤子機制適合注入點多,松耦合需求高的插件場景,能夠減少整個系統中插件調度的復雜度。成本就是額外引了一套鉤子機制了,不算高的成本,但也不是必要的。

使用者調度機制

這種模式本質就是將插件提供的能力,統一作為系統的額外能力對外透出,最后又系統的開發使用者決定什么時候調用。例如 JQuery 的插件會注冊 fn 中的額外行為,或者是 Egg 的插件可以向上下文中注冊額外的接口能力等。這種模式我個人認為比較適合又需要定制更多對外能力,又需要對能力的出口做收口的場景。如果你希望用戶通過統一的模式調用你的能力,那大可嘗試一下。你可以嘗試使用新的 Proxy 特性來實現這種模式。不管是系統對插件的調用還是插件調用系統的能力,我們都是需要一個確定的輸入輸出信息的,這也是我們上面 API 簽名所覆蓋到的信息。我們會在下一部分專門講一講。

插件輸入輸出的含義與可以使用的能力

插件與系統間最重要的契約就是 API 簽名,這涉及了可以使用哪些 API,以及這些 API 的輸入輸出是什么。

可以使用的能力

是指插件的邏輯可以使用的公共工具,或者可以通過一些方式獲取或影響系統本身的狀態。能力的注入我們常使用的方式是參數、上下文對象或者工廠函數閉包。提供的能力類型主要有下面四種:

  •  純工具:不影響系統狀態
  •  獲取當前系統狀態
  •  修改當前系統狀態
  •  API 形式注入功能:例如注冊 UI,注冊事件等

對于需要提供哪些能力,一般的建議是根據插件需要完成的工作,提供最小夠用范圍內的能力,盡量減少插件破壞系統的可能性。在部分場景下,如果不能通過 API 有效控制影響范圍,可以考慮為插件創造沙箱環境,比如插件內可能會調用 global 的接口等。

輸入輸出

當我們的插件是處在我們系統一個特定的處理邏輯流程中的(常見于直接調用機制或鉤子機制),我們的插件重點關注的就是輸入與輸出。此時的輸入與輸出一定是由邏輯流程本身所處的邏輯來決定的。輸入輸出的結構需要與插件的職責強關聯,盡量保證可序列化能力(為了防止過度膨脹以及本身的易讀性),并根據調度模式有額外的限制條件(下面會講)。如果你的插件輸入輸出過于復雜,可能要反思一下抽象是否過于粗粒度了。另外還需要對插件邏輯保證異常捕捉,防止對系統本身的破壞。還是 Babel Parser 那個例子。 

  1.  
  2.   parseExprAtom(refExpressionErrors: ?ExpressionErrors): N.Expression;  
  3.   getTokenFromCode(code: number): void; // 內部再調用 finishToken 來影響邏輯  
  4.   updateContext(prevType: TokenType): void; // 內部通過修改 this.state 來改變上下文信息  

意料之中的輸入,堅信不疑的輸出

復數個插件之間的關系是怎么樣的

Each plugin should only do a small amount of work, so you can connect them like building blocks. You may need to combine a bunch of them to get the desired result.

這里我們討論的是,在同一個擴展點上注入的插件,應該以什么形式做組合。常見的形式如下:

覆蓋式

只執行最新注冊的邏輯,跳過原始邏輯

管道式

輸入輸出相互銜接,一般輸入輸出是同一個數據類型。

洋蔥圈式

在管道式的基礎上,如果系統核心邏輯處于中間,插件同時關注進與出的邏輯,則可以使用洋蔥圈模型。

這里也可以參考 koa 中的中間件調度模式 https://github.com/koajs/compose[2] 

  1. const middleware = async (...params, next) => {  
  2.   // before  
  3.   await next();  
  4.   // after  
  5. }; 

集散式

集散式就是每一個插件都會執行,如果有輸出則最終將結果進行合并。這里的前提是存在方案,可以對執行結果進行 merge。

另外調度還可以分為 同步 和 異步 兩個方式,主要看插件邏輯是否包含異步行為。同步的實現會簡單一點,不過如果你不能確定,那也可以考慮先把異步的一起考慮進來。類似 https://www.npmjs.com/package/neo-async[3] 這樣的工具可以很好地幫助你。如果你使用了 tapble,那里面已經有相應的定義。另外還需要注意的細節是:

  •  順序是先注冊先執行,還是反過來,需要給到明確的解釋或一致的認知。
  •  同一個插件重復注冊了該怎么處理。

總結

當你跟著這篇文章的思路,把這些問題都思考清楚之后,想必你的腦海中一定已經有了一個插件架構的雛形了。剩下的可能是結合具體問題,再通過一些設計模式去優化開發者的體驗了。個人認為設計一個插件架構,是一定逃不開針對這些問題的思考的,而且只有去真正關注這些問題,才能避開炫技、過度設計等面向未來開發時時常會犯的錯誤。當然可能還差一些東西,一些推薦的實現方式也可能會過時,這些就歡迎大家幫忙指正啦。 

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2015-07-08 14:18:44

可擴展架構設計云計算

2011-11-09 13:46:51

可擴展架構

2011-05-13 09:13:34

博科虛擬化

2013-03-22 15:55:22

Web架構架構

2017-11-23 15:22:02

開源FIT2CLOUD混合云

2025-06-18 07:32:16

SpringJar動態加載

2013-08-20 10:01:12

橫向擴展架構IT趨勢數據中心

2022-04-11 09:15:00

前端開發技術

2013-05-27 10:58:28

Tumblr架構設計雅虎收購

2023-08-27 16:11:35

數據庫分布式事務數據庫

2010-01-15 10:15:34

分布式交換技術

2022-06-02 10:35:20

架構驅動

2009-06-12 16:07:05

演進式架構設計敏捷開發

2021-09-05 11:45:11

存儲

2022-09-28 17:59:03

MQ消息中間件

2012-07-02 14:47:57

架構敏捷開發

2011-08-29 10:59:47

QtWebkit嵌入式

2015-06-02 04:17:44

架構設計審架構設計說明書

2025-04-15 04:00:00

2025-05-09 08:45:13

點贊
收藏

51CTO技術棧公眾號

国产999精品视频| 91精品久久| 精品成人av| 欧美在线看片| 欧美伊人久久久久久久久影院| 国产精品日韩一区二区| 天堂网中文在线观看| 日夜干在线视频| 亚洲无线视频| 日韩美女一区二区三区| 最新视频 - x88av| 在线播放一级片| 精品国产精品| 91久久精品一区二区三区| 老牛影视免费一区二区| 伊人国产在线观看| 一本一道久久a久久| 亚洲欧美偷拍卡通变态| 成人女保姆的销魂服务| 国产又色又爽又高潮免费| 国产污视频在线播放| 福利一区在线观看| 欧美日本亚洲视频| 亚洲精品mv在线观看| 日本中文字幕电影在线免费观看| 日本不卡免费在线视频| 国产香蕉一区二区三区在线视频 | 91国产精品视频在线观看| 亚洲av无码专区在线| 懂色av蜜臀av粉嫩av永久| 久久99精品久久| 国产精品美女久久久浪潮软件| 日韩视频一区在线观看| av动漫免费观看| 最新在线中文字幕| 色777狠狠狠综合伊人| 成人av在线资源网| 久久免费视频在线观看| 无码人妻一区二区三区一| 中文字幕中文字幕在线十八区 | 亚洲欧洲精品一区二区精品久久久| 久青草国产97香蕉在线视频| 国产又黄又猛的视频| 蜜桃视频在线观看www社区| 久久99久久99精品免视看婷婷 | 91麻豆精品国产91久久久久久| 亚洲国产日韩美| 国产巨乳在线观看| 欧美日本亚洲韩国国产| 亚洲精品久久久久久久久| 成人免费在线小视频| 福利在线视频导航| 国产在线日韩欧美| 97超级碰碰人国产在线观看| 精品国产成人亚洲午夜福利| 只有精品亚洲| 香蕉成人伊视频在线观看| 日本一区二区免费看| 99国产精品欲| 久久只有精品| 欧美激情视频网| 无码一区二区三区在线| 亚洲日产av中文字幕| 91精品国产91久久综合桃花| 日本中文字幕网址| 欧美三级理伦电影| 国产精品国产三级国产普通话蜜臀| 成人在线观看av| 中文字字幕在线观看| 亚洲国产专区校园欧美| 日韩色av导航| 免费在线观看你懂的| 精品999日本久久久影院| 色综合久久久久综合体桃花网| 永久免费在线看片视频| av在线导航| 国产欧美日韩三级| 国产在线资源一区| 国产精品永久久久久久久久久| 国内外成人在线视频| 国产精品高潮在线| 91视频免费网址| 欧美在线不卡| 午夜精品一区二区三区在线| 亚洲 欧美 变态 另类 综合| 国内精品伊人久久久| 亚洲国产成人精品电影| 黄色片子免费看| 欧美亚洲人成在线| 欧洲一区在线电影| 哪个网站能看毛片| 九色porny自拍视频在线播放 | 麻豆亚洲av成人无码久久精品| 禁断一区二区三区在线| 丝袜情趣国产精品| 中文字幕人妻一区二区三区在线视频| aaa国产精品视频| 制服视频三区第一页精品| 岛国精品一区二区三区| 久久伊人精品| 亚洲剧情一区二区| 成年人在线观看av| 亚洲成人最新网站| 精品国产一区二区三区四区在线观看 | 国产原厂视频在线观看| 曰韩精品一区二区| http;//www.99re视频| 亚洲在线视频播放| 东方欧美亚洲色图在线| 亚洲自拍偷拍福利| 农村少妇久久久久久久| 亚洲精品aaaaa| 日韩一区二区在线视频| 国产香蕉在线视频| 精品一区二区三区蜜桃| 久久综合伊人77777麻豆| 国产黄色在线观看| 欧美性做爰猛烈叫床潮| 午夜精品福利在线观看| 中文字幕日日夜夜| 91免费小视频| 国产精品一区而去| 在线观看国产原创自拍视频| 中文字幕精品在线不卡| 亚洲v欧美v另类v综合v日韩v| 久青草国产在线| 国产亚洲欧美日韩在线一区| 欧美日韩另类丝袜其他| jizz日韩| 亚洲人成网站精品片在线观看 | 色婷婷免费视频| 久久久久久久久久久久久久久久久久久久| 日韩精品中文字幕在线一区| 蜜桃av乱码一区二区三区| 在线视频免费在线观看一区二区| 欧美一区二区三区艳史| www.久久久久久久| 久久精品久久精品| 国产高清精品一区二区| 久操视频在线观看| 在线不卡的av| 污污内射在线观看一区二区少妇| 久久av中文| 日韩亚洲欧美中文高清在线| 国产成人无码av| 久久机这里只有精品| 日本精品一区二区三区不卡无字幕| 美女露胸视频在线观看| 亚洲精品久久久久| 日本在线播放视频| 九九视频精品免费| 亚洲激情电影在线| 国产精品久久乐| 精品欧美一区二区三区精品久久 | 国产精品区一区二区三区| 自拍偷拍一区二区三区| 3344国产永久在线观看视频| 在线精品视频免费播放| 91精品人妻一区二区三区蜜桃欧美| 久久亚洲国产| 欧美一级免费视频| 日本福利片高清在线观看| 日韩欧美999| 亚洲日本黄色片| 欧美激情影院| 日韩中文字幕精品视频| 91免费视频播放| 9人人澡人人爽人人精品| 亚洲午夜精品福利| 手机在线观看av| 欧美美女激情18p| 国产美女久久久久久| 国产精品系列在线观看| 亚洲不卡1区| 福利一区和二区| 亚洲精品国产suv| 亚洲国产成人精品女人久久| 国产精品三级视频| 中文字幕一二三| 色爱综合网欧美| 亚洲xxx视频| 97电影在线| 宅男在线国产精品| 国产乡下妇女做爰| 国产成人日日夜夜| 在线看视频不卡| 136福利精品导航| 18性欧美xxxⅹ性满足| а天堂8中文最新版在线官网| 91精品福利在线一区二区三区| 国产奶水涨喷在线播放| 国产精品区一区二区三区| 欧洲视频一区二区三区| 999精品视频在线观看| 久久久久久欧美| 日韩精品在线免费看| 国产丝袜在线精品| 免费在线观看日韩av| 久久国产一二区| 精品91免费| 看电影就来5566av视频在线播放| 色欧美日韩亚洲| 日韩a级片在线观看| 奇米影视一区二区三区小说| 日本久久高清视频| 视频精品在线观看| 成人午夜电影免费在线观看| 日本综合视频| 国产精品性做久久久久久| 欧美做受777cos| 在线免费观看亚洲| 91成人精品网站| 成人免费看片| 在线一区二区日韩| 中文字幕无线码一区| 亚洲图片自拍偷拍| 永久免费未满蜜桃| 一区二区精品| 国产又黄又爽免费视频| 国产精品午夜一区二区三区| 国产91色在线|亚洲| 婷婷丁香久久| 国产精品露脸av在线| 最新国产在线观看| 日韩电影中文 亚洲精品乱码 | 天天色天天干天天色| 日本美女一区二区| 免费黄色特级片| 精品久久电影| 久久影院理伦片| 国产精品白丝av嫩草影院| 91成人在线视频| av老司机免费在线| 国内伊人久久久久久网站视频| 在线中文字幕电影| 美女久久久久久久| 手机福利在线| 欧美色爱综合网| 超碰在线国产97| 日韩一区欧美小说| 老司机精品免费视频| 国产清纯美女被跳蛋高潮一区二区久久w | 久久精品久久国产| 亚洲一级在线观看| 久久国产精品波多野结衣| 一区二区三区在线视频免费 | 国产中文字幕91| av在线播放观看| 精品国偷自产在线| 成人毛片在线免费观看| 色天使色偷偷av一区二区| 亚洲黄色三级视频| 中文字幕一区二区三区色视频| 久久久久亚洲av成人无码电影| 99精品在线免费| 性生活免费在线观看| 黄色综合网站| av日韩一区二区三区| 欧美综合另类| 国产精选在线观看91| 加勒比久久高清| 免费中文日韩| 不卡在线一区二区| 超碰免费在线公开| 欧美在线免费一级片| 拔插拔插海外华人免费| 国产欧美精品久久| 亚洲 中文字幕 日韩 无码| 欧美1级日本1级| www.好吊操| 亚洲草久电影| 福利视频一区二区三区四区| 久久精品电影| 精品综合久久久久| 国产成人av一区二区三区在线观看| 欧美精品一区二区三区在线四季| 欧美有码在线| 婷婷久久伊人| 这里只有精品在线| 99视频在线免费观看| 超碰成人在线观看| 日本黑人久久| 一区二区三区中文| 每日在线更新av| 久草在现在线| 精品婷婷伊人一区三区三| 91 中文字幕| 亚洲国产精品国自产拍av秋霞| 黄色毛片在线看| 欧美另类xxx| 欧美成人性网| 欧美最猛性xxxxx亚洲精品| 欧美家庭影院| 久久久极品av| 男人av在线播放| 91精品国产自产在线| 中文在线字幕av| 国产成a人亚洲| 成年人网站免费看| 中文字幕一区视频| 97免费在线观看视频| 欧美日韩视频第一区| 日批免费在线观看| 欧美在线小视频| www久久久久久| 日韩欧美国产系列| 57pao国产成永久免费视频| 精品无人区卡一卡二卡三乱码免费卡 | yellow字幕网在线| 国产精品日韩欧美综合| 国产精品国产| 黄色网zhan| 日本视频在线一区| 男人的天堂影院| 亚洲女人的天堂| www.4hu95.com四虎| 欧美激情在线看| 日韩欧美视频在线免费观看| 欧美猛男男办公室激情| 欧美日韩影视| 午夜精品在线视频| 另类视频一区二区三区| 亚洲成人自拍视频| 水野朝阳av一区二区三区| 亚洲婷婷在线观看| 亚洲精品视频一区二区| 一区二区小视频| 国产亚洲成精品久久| 性欧美freesex顶级少妇| 国产精品久久久久久免费观看| 亚洲欧美网站在线观看| 69久久久久久| 欧美久久久久免费| 精品国产乱码一区二区| 欧美一级片在线观看| 幼a在线观看| 国产精品福利在线观看| 男男gay无套免费视频欧美| 国产精品久久久久7777| 大美女一区二区三区| 青娱乐免费在线视频| 欧美一级高清片| 亚洲h片在线看| 精品国产一区二区三区久久狼5月| 香蕉精品久久| 国产美女久久精品| 国产高清亚洲| 国产青春久久久国产毛片| 欧美激情aⅴ一区二区三区| 永久免费黄色片| 亚洲欧美色综合| 成人av手机在线| 欧美激情日韩图片| 精品人人人人| 国产中文字幕视频在线观看| 国产亚洲综合久久| 色呦呦免费观看| 欧美黑人狂野猛交老妇| 69精品国产久热在线观看| 日本高清xxxx| 成人一区二区三区在线观看| 亚洲国产精品成人无久久精品| 日韩精品亚洲视频| 久草福利在线视频| 国产成人精品免费久久久久 | 国产深夜精品福利| 久久久久蜜桃| 性一交一黄一片| 婷婷综合在线观看| 一级黄色片免费看| 久久九九免费视频| 97久久亚洲| 国产二区视频在线播放| 国产欧美日韩精品一区| 国产精品一区二区黑人巨大| 欧美激情2020午夜免费观看| 欧美国产不卡| 国产九九在线视频| 97精品国产露脸对白| 日日夜夜狠狠操| 久久精品99久久久香蕉| 成人性生交大片免费看中文视频 | 日产精品一线二线三线芒果| 国产精品黄色| 国产精品815.cc红桃| 9191国产精品| 亚洲精品日产| 一本一生久久a久久精品综合蜜| 国产99久久久国产精品| 亚洲黄网在线观看| 欧美日韩xxxxx| 国产一区二区三区站长工具| 久久久福利影院| 色综合一区二区| 91精品久久久久久粉嫩| 日本一区二区三区视频免费看| 国产麻豆精品视频| 无码人妻熟妇av又粗又大| 欧美日韩福利在线观看| 国产a久久精品一区二区三区 |