亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

Javascript技術棧中的四種依賴注入小結

2019-11-20 10:29:42
字體:
來源:轉載
供稿:網友

作為面向對象編程中實現控制反轉(Inversion of Control,下文稱IoC)最常見的技術手段之一,依賴注入(Dependency Injection,下文稱DI)可謂在OOP編程中大行其道經久不衰。比如在J2EE中,就有大名鼎鼎的執牛耳者Spring。Javascript社區中自然也不乏一些積極的嘗試,廣為人知的AngularJS很大程度上就是基于DI實現的。遺憾的是,作為一款缺少反射機制、不支持Annotation語法的動態語言,Javascript長期以來都沒有屬于自己的Spring框架。當然,伴隨著ECMAScript草案進入快速迭代期的春風,Javascript社區中的各種方言、框架可謂群雄并起,方興未艾。可以預見到,優秀的JavascriptDI框架的出現只是早晚的事。

本文總結了Javascript中常見的依賴注入方式,并以inversify.js為例,介紹了方言社區對于Javascript中DI框架的嘗試和初步成果。文章分為四節:

一. 基于Injector、Cache和函數參數名的依賴注入
二. AngularJS中基于雙Injector的依賴注入
三. TypeScript中基于裝飾器和反射的依賴注入
四. inversify.js――Javascript技術棧中的IoC容器

一. 基于Injector、Cache和函數參數名的依賴注入

盡管Javascript中不原生支持反射(Reflection)語法,但是Function.prototype上的toString方法卻為我們另辟蹊徑,使得在運行時窺探某個函數的內部構造成為可能:toString方法會以字符串的形式返回包含function關鍵字在內的整個函數定義。從這個完整的函數定義出發,我們可以利用正則表達式提取出該函數所需要的參數,從而在某種程度上得知該函數的運行依賴。
比如Student類上write方法的函數簽名write(notebook, pencil)就說明它的執行依賴于notebook和pencil對象。因此,我們可以首先把notebook和pencil對象存放到某個cache中,再通過injector(注入器、注射器)向write方法提供它所需要的依賴:

var cache = {};// 通過解析Function.prototype.toString()取得參數名function getParamNames(func) {  // 正則表達式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript  var paramNames = func.toString().match(/^function/s*[^/(]*/(/s*([^/)]*)/)/m)[1];  paramNames = paramNames.replace(/ /g, '');  paramNames = paramNames.split(',');  return paramNames;}var injector = {  // 將func作用域中的this關鍵字綁定到bind對象上,bind對象可以為空  resolve: function (func, bind) {    // 取得參數名    var paramNames = getParamNames(func);    var params = [];    for (var i = 0; i < paramNames.length; i++) {      // 通過參數名在cache中取出相應的依賴      params.push(cache[paramNames[i]]);    }    // 注入依賴并執行函數    func.apply(bind, params);  }}; function Notebook() {}Notebook.prototype.printName = function () {  console.log('this is a notebook');}; function Pencil() {}Pencil.prototype.printName = function () {  console.log('this is a pencil');}; function Student() {}Student.prototype.write = function (notebook, pencil) {  if (!notebook || !pencil) {    throw new Error('Dependencies not provided!');  }  console.log('writing...');};// 提供notebook依賴cache['notebook'] = new Notebook();// 提供pencil依賴cache['pencil'] = new Pencil();var student = new Student();injector.resolve(student.write, student); // writing...

有時候為了保證良好的封裝性,也不一定要把cache對象暴露給外界作用域,更多的時候是以閉包變量或者私有屬性的形式存在的:

function Injector() {  this._cache = {};} Injector.prototype.put = function (name, obj) {  this._cache[name] = obj;}; Injector.prototype.getParamNames = function (func) {  // 正則表達式出自http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript  var paramNames = func.toString().match(/^function/s*[^/(]*/(/s*([^/)]*)/)/m)[1];  paramNames = paramNames.replace(/ /g, '');  paramNames = paramNames.split(',');  return paramNames;}; Injector.prototype.resolve = function (func, bind) {  var self = this;  var paramNames = self.getParamNames(func);  var params = paramNames.map(function (name) {    return self._cache[name];  });  func.apply(bind, params);}; var injector = new Injector(); var student = new Student();injector.put('notebook', new Notebook());injector.put('pencil', new Pencil())injector.resolve(student.write, student); // writing...

比如現在要執行Student類上的另一個方法function draw(notebook, pencil, eraser),因為injector的cache中已經有了notebook和pencil對象,我們只需要將額外的eraser也存放到cache中:

function Eraser() {}Eraser.prototype.printName = function () {  console.log('this is an eraser');}; // 為Student增加draw方法Student.prototype.draw = function (notebook, pencil, eraser) {  if (!notebook || !pencil || !eraser) {    throw new Error('Dependencies not provided!');  }  console.log('drawing...');}; injector.put('eraser', new Eraser());injector.resolve(student.draw, student);

通過依賴注入,函數的執行和其所依賴對象的創建邏輯就被解耦開來了。
當然,隨著grunt/gulp/fis等前端工程化工具的普及,越來越多的項目在上線之前都經過了代碼混淆(uglify),因而通過參數名去判斷依賴并不總是可靠,有時候也會通過為function添加額外屬性的方式來明確地說明其依賴:

Student.prototype.write.depends = ['notebook', 'pencil'];Student.prototype.draw.depends = ['notebook', 'pencil', 'eraser'];Injector.prototype.resolve = function (func, bind) {  var self = this;  // 首先檢查func上是否有depends屬性,如果沒有,再用正則表達式解析  func.depends = func.depends || self.getParamNames(func);  var params = func.depends.map(function (name) {    return self._cache[name];  });  func.apply(bind, params);};var student = new Student();injector.resolve(student.write, student); // writing...injector.resolve(student.draw, student); // draw...

二. AngularJS中基于雙Injector的依賴注入

熟悉AngularJS的同學很快就能聯想到,在injector注入之前,我們在定義module時還可以調用config方法來配置隨后會被注入的對象。典型的例子就是在使用路由時對$routeProvider的配置。也就是說,不同于上一小節中直接將現成對象(比如new Notebook())存入cache的做法,AngularJS中的依賴注入應該還有一個”實例化”或者”調用工廠方法”的過程。
這就是providerInjector、instanceInjector以及他們各自所擁有的providerCache和instanceCache的由來。
在AngularJS中,我們能夠通過依賴注入獲取到的injector通常是instanceInjector,而providerInjector則是以閉包中變量的形式存在的。每當我們需要AngularJS提供依賴注入服務時,比如想要獲取notebook,instanceInjector會首先查詢instanceCache上是存在notebook屬性,如果存在,則直接注入;如果不存在,則將這個任務轉交給providerInjector;providerInjector會將”Provider”字符串拼接到”notebook”字符串的后面,組成一個新的鍵名”notebookProvider”,再到providerCache中查詢是否有notebookProvider這個屬性,如有沒有,則拋出異常Unknown Provider異常:

如果有,則將這個provider返回給instanceInjector;instanceInjector拿到notebookProvider后,會調用notebookProvider上的工廠方法$get,獲取返回值notebook對象,將該對象放到instanceCache中以備將來使用,同時也注入到一開始聲明這個依賴的函數中。

需要注意的是,AngularJS中的依賴注入方式也是有缺陷的:利用一個instanceInjector單例服務全局的副作用就是無法單獨跟蹤和控制某一條依賴鏈條,即使在沒有交叉依賴的情況下,不同module中的同名provider也會產生覆蓋,這里就不詳細展開了。

另外,對于習慣于Java和C#等語言中高級IoC容器的同學來說,看到這里可能覺得有些別扭,畢竟在OOP中,我們通常不會將依賴以參數的形式傳遞給方法,而是作為屬性通過constructor或者setters傳遞給實例,以實現封裝。的確如此,一、二節中的依賴注入方式沒有體現出足夠的面向對象特性,畢竟這種方式在Javascript已經存在多年了,甚至都不需要ES5的語法支持。希望了解Javascript社區中最近一兩年關于依賴注入的研究和成果的同學,可以繼續往下閱讀。

三. TypeScript中基于裝飾器和反射的依賴注入

博主本身對于Javascript的各種方言的學習并不是特別熱情,尤其是現在EMCAScript提案、草案更新很快,很多時候借助于polyfill和babel的各種preset就能滿足需求了。但是TypeScript是一個例外(當然現在Decorator也已經是提案了,雖然階段還比較早,但是確實已經有polyfill可以使用)。上文提到,Javascript社區中遲遲沒有出現一款優秀的IoC容器和自身的語言特性有關,那就依賴注入這個話題而言,TypeScript給我們帶來了什么不同呢?至少有下面這幾點:
* TypeScript增加了編譯時類型檢查,使Javascript具備了一定的靜態語言特性
* TypeScript支持裝飾器(Decorator)語法,和傳統的注解(Annotation)頗為相似
* TypeScript支持元信息(Metadata)反射,不再需要調用Function.prototype.toString方法
下面我們就嘗試利用TypeScript帶來的新語法來規范和簡化依賴注入。這次我們不再向函數或方法中注入依賴了,而是向類的構造函數中注入。
TypeScript支持對類、方法、屬性和函數參數進行裝飾,這里需要用到的是對類的裝飾。繼續上面小節中用到的例子,利用TypeScript對代碼進行一些重構:

class Pencil {  public printName() {    console.log('this is a pencil');  }} class Eraser {  public printName() {    console.log('this is an eraser');  }} class Notebook {  public printName() {    console.log('this is a notebook');  }} class Student {  pencil: Pencil;  eraser: Eraser;  notebook: Notebook;  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {    this.notebook = notebook;    this.pencil = pencil;    this.eraser = eraser;  }  public write() {    if (!this.notebook || !this.pencil) {      throw new Error('Dependencies not provided!');    }    console.log('writing...');  }  public draw() {    if (!this.notebook || !this.pencil || !this.eraser) {      throw new Error('Dependencies not provided!');    }    console.log('drawing...');  }}

下面是injector和裝飾器Inject的實現。injector的resolve方法在接收到傳入的構造函數時,會通過name屬性取出該構造函數的名字,比如class Student,它的name屬性就是字符串”Student”。再將Student作為key,到dependenciesMap中去取出Student的依賴,至于dependenciesMap中是何時存入的依賴關系,這是裝飾器Inject的邏輯,后面會談到。Student的依賴取出后,由于這些依賴已經是構造函數的引用而非簡單的字符串了(比如Notebook、Pencil的構造函數),因此直接使用new語句即可獲取這些對象。獲取到Student類所依賴的對象之后,如何把這些依賴作為構造函數的參數傳入到Student中呢?最簡單的莫過于ES6的spread操作符。在不能使用ES6的環境下,我們也可以通過偽造一個構造函數來完成上述邏輯。注意為了使instanceof操作符不失效,這個偽造的構造函數的prototype屬性應該指向原構造函數的prototype屬性。

var dependenciesMap = {};var injector = {  resolve: function (constructor) {    var dependencies = dependenciesMap[constructor.name];    dependencies = dependencies.map(function (dependency) {      return new dependency();    });    // 如果可以使用ES6的語法,下面的代碼可以合并為一行:    // return new constructor(...dependencies);    var mockConstructor: any = function () {      constructor.apply(this, dependencies);    };    mockConstructor.prototype = constructor.prototype;    return new mockConstructor();  }};function Inject(...dependencies) {  return function (constructor) {    dependenciesMap[constructor.name] = dependencies;    return constructor;  };}

injector和裝飾器Inject的邏輯完成后,就可以用來裝飾class Student并享受依賴注入帶來的樂趣了:

// 裝飾器的使用非常簡單,只需要在類定義的上方添加一行代碼// Inject是裝飾器的名字,后面是function Inject的參數@Inject(Notebook, Pencil, Eraser)class Student {  pencil: Pencil;  eraser: Eraser;  notebook: Notebook;  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {    this.notebook = notebook;    this.pencil = pencil;    this.eraser = eraser;  }  public write() {    if (!this.notebook || !this.pencil) {      throw new Error('Dependencies not provided!');    }    console.log('writing...');  }  public draw() {    if (!this.notebook || !this.pencil || !this.eraser) {      throw new Error('Dependencies not provided!');    }    console.log('drawing...');  }}var student = injector.resolve(Student);console.log(student instanceof Student); // truestudent.notebook.printName(); // this is a notebookstudent.pencil.printName(); // this is a pencilstudent.eraser.printName(); // this is an eraserstudent.draw(); // drawingstudent.write(); // writing

利用裝飾器,我們還可以實現一種比較激進的依賴注入,下文稱之為RadicalInject。RadicalInject對原代碼的侵入性比較強,不一定適合具體的業務,這里也一并介紹一下。要理解RadicalInject,需要對TypeScript裝飾器的原理和Array.prototype上的reduce方法理解比較到位。

function RadicalInject(...dependencies){  var wrappedFunc:any = function (target: any) {    dependencies = dependencies.map(function (dependency) {      return new dependency();    });    // 使用mockConstructor的原因和上例相同    function mockConstructor() {      target.apply(this, dependencies);    }    mockConstructor.prototype = target.prototype;     // 為什么需要使用reservedConstructor呢?因為使用RadicalInject對Student方法裝飾之后,    // Student指向的構造函數已經不是一開始我們聲明的class Student了,而是這里的返回值,    // 即reservedConstructor。Student的指向變了并不是一件不能接受的事,但是如果要    // 保證student instanceof Student如我們所期望的那樣工作,這里就應該將    // reservedConstructor的prototype屬性指向原Student的prototype    function reservedConstructor() {      return new mockConstructor();    }    reservedConstructor.prototype = target.prototype;    return reservedConstructor;  }  return wrappedFunc;}

使用RadicalInject,原構造函數實質上已經被一個新的函數代理了,使用上也更為簡單,甚至都不需要再有injector的實現:

@RadicalInject(Notebook, Pencil, Eraser)class Student {  pencil: Pencil;  eraser: Eraser;  notebook: Notebook;  public constructor() {}  public constructor(notebook: Notebook, pencil: Pencil, eraser: Eraser) {    this.notebook = notebook;    this.pencil = pencil;    this.eraser = eraser;  }  public write() {    if (!this.notebook || !this.pencil) {      throw new Error('Dependencies not provided!');    }    console.log('writing...');  }  public draw() {    if (!this.notebook || !this.pencil || !this.eraser) {      throw new Error('Dependencies not provided!');    }    console.log('drawing...');  }}// 不再出現injector,直接調用構造函數var student = new Student(); console.log(student instanceof Student); // truestudent.notebook.printName(); // this is a notebookstudent.pencil.printName(); // this is a pencilstudent.eraser.printName(); // this is an eraserstudent.draw(); // drawingstudent.write(); // writing

由于class Student的constructor方法需要接收三個參數,直接無參調用new Student()會造成TypeScript編譯器報錯。當然這里只是分享一種思路,大家可以暫時忽略這個錯誤。有興趣的同學也可以使用類似的思路嘗試代理一個工廠方法,而非直接代理構造函數,以避免這類錯誤,這里不再展開。

AngularJS2團隊為了獲得更好的裝飾器和反射語法的支持,一度準備另起爐灶,基于AtScript(AtScript中的”A”指的就是Annotation)來進行新框架的開發。但最終卻選擇擁抱TypeScript,于是便有了微軟和谷歌的奇妙組合。

當然,需要說明的是,在缺少相關標準和瀏覽器廠商支持的情況下,TypeScript在運行時只是純粹的Javascript,下節中出現的例子會印證這一點。

四. inversify.js――Javascript技術棧中的IoC容器

其實從Javascript出現各種支持高級語言特性的方言就可以預見到,IoC容器的出現只是早晚的事情。比如博主今天要介紹的基于TypeScript的inversify.js,就是其中的先行者之一。
inversity.js比上節中博主實現的例子還要進步很多,它最初設計的目的就是為了前端工程師同學們能在Javascript中寫出符合SOLID原則的代碼,立意可謂非常之高。表現在代碼中,就是處處有接口,將”Depend upon Abstractions. Do not depend upon concretions.”(依賴于抽象,而非依賴于具體)表現地淋漓盡致。繼續使用上面的例子,但是由于inversity.js是面向接口的,上面的代碼需要進一步重構:

interface NotebookInterface {  printName(): void;}interface PencilInterface {  printName(): void;}interface EraserInterface {  printName(): void;}interface StudentInterface {  notebook: NotebookInterface;  pencil: PencilInterface;  eraser: EraserInterface;  write(): void;  draw(): void;}class Notebook implements NotebookInterface {  public printName() {    console.log('this is a notebook');  }}class Pencil implements PencilInterface {  public printName() {    console.log('this is a pencil');  }}class Eraser implements EraserInterface {  public printName() {    console.log('this is an eraser');  }} class Student implements StudentInterface {  notebook: NotebookInterface;  pencil: PencilInterface;  eraser: EraserInterface;  constructor(notebook: NotebookInterface, pencil: PencilInterface, eraser: EraserInterface) {    this.notebook = notebook;    this.pencil = pencil;    this.eraser = eraser;  }  write() {    console.log('writing...');  }  draw() {    console.log('drawing...');  }}

由于使用了inversity框架,這次我們就不用自己實現injector和Inject裝飾器啦,只需要從inversify模塊中引用相關對象:

import { Inject } from "inversify"; @Inject("NotebookInterface", "PencilInterface", "EraserInterface")class Student implements StudentInterface {  notebook: NotebookInterface;  pencil: PencilInterface;  eraser: EraserInterface;  constructor(notebook: NotebookInterface, pencil: PencilInterface, eraser: EraserInterface) {    this.notebook = notebook;    this.pencil = pencil;    this.eraser = eraser;  }  write() {    console.log('writing...');  }  draw() {    console.log('drawing...');  }}

這樣就行了嗎?還記得上節中提到TypeScript中各種概念只是語法糖嗎?不同于上一節中直接將constructor引用傳遞給Inject的例子,由于inversify.js是面向接口的,而諸如NotebookInterface、PencilInterface之類的接口只是由TypeScript提供的語法糖,在運行時并不存在,因此我們在裝飾器中聲明依賴時只能使用字符串形式而非引用形式。不過不用擔心,inversify.js為我們提供了bind機制,在接口的字符串形式和具體的構造函數之間搭建了橋梁:

import { TypeBinding, Kernel } from "inversify"; var kernel = new Kernel();kernel.bind(new TypeBinding("NotebookInterface", Notebook));kernel.bind(new TypeBinding("PencilInterface", Pencil));kernel.bind(new TypeBinding("EraserInterface", Eraser));kernel.bind(new TypeBinding("StudentInterface", Student));

注意這步需要從inversify模塊中引入TypeBinding和Kernel,并且為了保證返回值類型以及整個編譯時靜態類型檢查能夠順利通過,泛型語法也被使用了起來。
說到這里,要理解new TypeBinding(“NotebookInterface”, Notebook)也就很自然了:為依賴于”NotebookInterface”字符串的類提供Notebook類的實例,返回值向上溯型到NotebookInterface。
完成了這些步驟,使用起來也還算順手:

var student: StudentInterface = kernel.resolve("StudentInterface");console.log(student instanceof Student); // truestudent.notebook.printName(); // this is a notebookstudent.pencil.printName(); // this is a pencilstudent.eraser.printName(); // this is an eraserstudent.draw(); // drawingstudent.write(); // writing

最后,順帶提一下ECMAScript中相關提案的現狀和進展。Google的AtScript團隊曾經有過Annotation的提案,但是AtScript胎死腹中,這個提案自然不了了之了。目前比較有希望成為es7標準的是一個關于裝飾器的提案:https://github.com/wycats/javascript-decorators。感興趣的同學可以到相關的github頁面跟蹤了解。盡管DI只是OOP編程眾多模式和特性中的一個,但卻可以折射出Javascript在OOP上艱難前進的道路。但總得說來,也算得上是路途坦蕩,前途光明。回到依賴注入的話題上,一邊是翹首以盼的Javascript社區,一邊是姍姍來遲的IoC容器,這二者最終能產生怎樣的化學反應,讓我們拭目以待。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品扒开腿爽爽爽视频| 久久中文字幕在线| 国模私拍一区二区三区| 久久国产色av| 91最新在线免费观看| 亚洲人成在线播放| 一本久久综合亚洲鲁鲁| 欧美成人精品一区二区三区| 在线亚洲午夜片av大片| 国产成人免费av电影| 91久久在线播放| 午夜剧场成人观在线视频免费观看| 在线视频欧美日韩精品| 91色视频在线观看| 欧美激情一级二级| 国产主播喷水一区二区| 国产亚洲欧美日韩一区二区| 九九热99久久久国产盗摄| 欧美精品在线极品| 久久av资源网站| 91免费高清视频| 少妇高潮久久久久久潘金莲| 国产精品看片资源| 久久九九热免费视频| 亚洲日韩中文字幕在线播放| 中文字幕久久久| 成人观看高清在线观看免费| 亚洲aa在线观看| 91免费观看网站| 成人网在线观看| 在线观看国产欧美| 欧美在线一级视频| 日韩黄色高清视频| 亚洲精品一区中文| 亚洲第一精品夜夜躁人人爽| 久久成人这里只有精品| 国产综合色香蕉精品| 午夜精品久久久久久久男人的天堂| 日韩精品极品在线观看播放免费视频| 欧美情侣性视频| 久久6免费高清热精品| 国产精品亚洲一区二区三区| 国产xxx69麻豆国语对白| 久久91亚洲精品中文字幕| 亚洲免费一在线| 国产精品精品视频一区二区三区| 日韩高清不卡av| 国产亚洲欧美日韩美女| 中文字幕亚洲情99在线| 久久视频在线观看免费| 在线精品国产成人综合| 精品国产欧美一区二区三区成人| 成人激情春色网| 精品香蕉在线观看视频一| 中文字幕最新精品| 国产女同一区二区| 欧美亚洲午夜视频在线观看| 日韩视频亚洲视频| 九九热这里只有在线精品视| 精品国内自产拍在线观看| 久久久精品2019中文字幕神马| 国产精品揄拍一区二区| 国产精品自拍小视频| 深夜成人在线观看| 韩国视频理论视频久久| 久久久999精品视频| 91香蕉亚洲精品| 高跟丝袜一区二区三区| 成人网在线视频| 成人av电影天堂| 国产成人精品免费久久久久| 国产精品香蕉在线观看| 国产精品亚洲精品| 97在线看免费观看视频在线观看| 国产综合久久久久| 日韩电影第一页| 国产精品久久久久久五月尺| 这里只有精品在线播放| 亚洲视频在线免费观看| 日韩亚洲一区二区| 综合欧美国产视频二区| 91精品啪aⅴ在线观看国产| 欧美激情在线观看| 精品久久久久久久中文字幕| 亚洲黄色片网站| 久久香蕉国产线看观看av| 91影院在线免费观看视频| 国产精品夫妻激情| 欧美精品videos另类日本| 国产精品久久一区主播| 亚洲欧美精品中文字幕在线| 国内自拍欧美激情| 亚洲第一天堂av| 欧美日韩国产999| 在线观看欧美日韩国产| 欧美亚洲另类激情另类| 精品高清美女精品国产区| 国产亚洲精品久久久优势| 欧美精品久久久久a| 在线丨暗呦小u女国产精品| 精品免费在线视频| 欧美激情按摩在线| 日本中文字幕久久看| 97视频在线观看视频免费视频| 欧美丰满少妇xxxxx做受| 国产精品白丝av嫩草影院| 91成人在线观看国产| 久久成人精品电影| 欧美日韩不卡合集视频| 91免费看片网站| www.99久久热国产日韩欧美.com| 精品美女永久免费视频| 亚洲免费中文字幕| 国产欧美精品在线| 日韩中文字幕国产| 茄子视频成人在线| 国产成人在线精品| 国产香蕉97碰碰久久人人| 亚洲精品国产精品国自产观看浪潮| 91久久嫩草影院一区二区| 欧美激情极品视频| www.亚洲天堂| 国产精品偷伦免费视频观看的| 国产日本欧美在线观看| 久久久精品欧美| 国产有码在线一区二区视频| 美女啪啪无遮挡免费久久网站| 亚洲偷欧美偷国内偷| 日本免费一区二区三区视频观看| 欧美日韩亚洲精品一区二区三区| 久久精品电影一区二区| 欧美尤物巨大精品爽| 成人性生交大片免费看视频直播| 88国产精品欧美一区二区三区| 亚洲国产精品字幕| 成人午夜在线视频一区| 精品香蕉一区二区三区| 国内精品一区二区三区| 亚洲精品www久久久久久广东| 欧美伊久线香蕉线新在线| 欧美黑人巨大xxx极品| 欧美激情伊人电影| 亚洲天堂第二页| 日韩精品在线观看视频| 97国产精品免费视频| 中文字幕日韩av| 午夜精品一区二区三区在线播放| 精品人伦一区二区三区蜜桃网站| 亚洲免费伊人电影在线观看av| 亚洲国内精品在线| 亚洲а∨天堂久久精品喷水| 久久久久日韩精品久久久男男| 91精品视频大全| 日本19禁啪啪免费观看www| 亚洲欧美一区二区三区四区| 最近2019中文字幕一页二页| 亚洲国产97在线精品一区| 日韩精品视频在线免费观看| 国产欧亚日韩视频| 成人黄色大片在线免费观看| 九九精品在线视频| 9.1国产丝袜在线观看| 欧美大学生性色视频| 精品国产91乱高清在线观看|