成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

參透Node中exports的7種設計模式

開發(fā) 前端
這篇文章試著要整理,翻譯Export This: Interface Design Patterns for Node.js Modules這篇非常值得一讀的文章。但因為這篇文章有些時日了,部分示例已經不符合現(xiàn)況。故這是一篇加上小弟收集匯整而成的更新翻譯。

前言

這篇文章試著要整理,翻譯Export This: Interface Design Patterns for Node.js Modules這篇非常值得一讀的文章。

但因為這篇文章有些時日了,部分示例已經不符合現(xiàn)況。故這是一篇加上小弟收集匯整而成的更新翻譯。

旅程的開始

當你在Node中加載一個模塊,我們到底會取回什么?當我們撰寫一個模塊時我們又有哪些選擇可以用來設計程序的界面?

在我***次學習Node的時候,發(fā)現(xiàn)在Node中有太多的方式處理這個問題,由于Javascript本身非常彈性,加上在社群中的開發(fā)者們各自都有不同的實作風格,這讓當時的我感到有點挫折。

在原文作者的學習旅程中曾持續(xù)的觀察尋找好的方式以應用在其的工作上,在這篇文章中將會分享觀察到的Node模塊設計方式。

大略總結了7種設計模式(pattern)

  • 導出命名空間Namespace
  • 導出函式Function
  • 導出高階函式High-Order Function
  • 導出構造函數(shù)/構建函式Constructor
  • 導出單一實例物件Singleton
  • 擴展全局物件Extend Global Object
  • 套用猴子補丁Monkey Patch

require,exports和module.exports

首先我們需要先聊點基礎的知識

在Node官方文件中定義了匯入一個檔案就是匯入一個模塊。

In Node.js,files and modules are in one-to-one correspondence.- Node文件

也就是所有的模塊會參考指向(Reference)一個隱式模塊物件的module.exports。當我們使用require()時會取得的東西。同時我們也取得exports。

這個exports就是指向module.exports的參考。exports會收集該其屬性,如果module.exports沒有任何屬性就把這些數(shù)據(jù)交給module.exports,但如果module.exports已經具備屬性的話,那么exports的所有數(shù)據(jù)都會被忽略。

為了讓您更好理解關于exports與module.exports下面的示例提供了較詳細的說明 

  1. var a = { id: 1 } 
  2. var b = a 
  3. console.log(a)// {id: 1} 
  4. console.log(b)// {id: 1} 
  5.  
  6. // b參考指向a,意味著修改b的屬性a會跟著變動 
  7. b.id = 2 
  8. console.log(a)// {id: 2} 
  9. console.log(b)// {id: 2} 
  10.  
  11. //但如果將一個全新的物件賦予b那么參考的關系將會中斷 
  12. b = { id: 3 } 
  13. console.log(a)// {id: 2} 
  14. console.log(b)// {id: 3}  

另外比較具體的示例 

  1. /* person.js */ 
  2. exports.name = function(){ 
  3.  console.log('My name is andyyou.') 
  4. … 
  5. /* main.js */ 
  6. var person = require('./person.js') 
  7. person.name() 
  8. /* person.js */ 
  9. module.exports = 'Hey,andyyou' 
  10. exports.name = function(){ 
  11.  console.log('My name is andyyou') 
  12.  
  13. /* main.js */ 
  14. var person = require('./person.js') 
  15. // exports的屬性被忽略了 
  16. person.name()// TypeError: Object Hey,andyyou has no method 'name'  
  • exports只是指向module.exports的參考(Reference)
  • module.exports初始值為{}空物件,于是exports也會取得該空物件
  • require()回傳的是module.exports而不是exports
  • 所以您可以使用exports.property_name = something而不會使用exports = something
  • 一旦使用exports = something參考關系便會停止,也就是exports的數(shù)據(jù)都會被忽略。

本質上我們可以理解為所有模塊都隱含實作了下面這行代碼

  1. var exports = module.exports = {} 

現(xiàn)在我們知道了,當我們要導出一個function時我們得使用module.exports。

如果使用exports那個exports的內存位置(Reference/參考)將會被修改而module.exports就不會得到其內容。

另外,我們在許多項目看到下面的這行代碼

  1. exports = module.exports = something 

這行代碼作用就是確保exports在module.exports被我們復寫之后,仍可以指向相同的參考。

接著我們就可以透過module.exports來定義并導出一個function

  1. /* function.js */ 
  2. module.exports = function(){ 
  3.  return { name'andyyou' } 
  4.  

使用的方式則是

  1. var func = require('./function'

關于require一個很重要的行為就是它會緩存(Cache)module.exports的值,未來每一次require被調用時都會回傳相同的值。

它會根據(jù)匯入檔案的絕對路徑來緩存,所以當我們想要模塊能夠回傳不同得值時,我們就需要導出function,如此一來每次執(zhí)行函式時就會回傳一個新值。

下面在Node REPL中簡易的示范 

  1. $ node 
  2. > f1 = require('/Users/andyyou/Projects/export_this/function') 
  3. [Function
  4. > f2 = require('./function')//相同路徑 
  5. [Function
  6. > f1 === f2 
  7. true 
  8. > f1()=== f2() 
  9. false  

您可以觀察到require回傳了同樣的函式物件實例,但每一次調用函式回傳的物件是不同的。

更詳細的介紹可以參考官方文件,值得一讀。

現(xiàn)在,我們可以開始探討界面的設計模式(pattern)了。

導出命名空間

一個簡單且常用的設計模式就是導出一個包含數(shù)個屬性的物件,這些屬性具體的內容主要是函式,但并不限于函式。

如此,我們就能夠透過匯入該模塊來取得這個命名空間下一系列相關的功能。

當您匯入一個命名空間類型的模塊時,我們通常會將模塊指定到某一個變數(shù),然后透過它的成員(物件屬性)來存取使用這些功能。

甚至我們也可以將這些變數(shù)成員直接指定到區(qū)域變數(shù)。

  1. var fs = require('fs') 
  2. var readFile = fs.readFile 
  3. var ReadStream = fs.ReadStream 
  4.  
  5. readFile('./file.txt'function(err,data){ 
  6.  console.log('readFile contents: %s',data) 
  7. })  

這便是fs核心模塊的做法

  1. var fs = exports 

首先將隱式exports物件設定一個區(qū)域變數(shù)(即上面提過的exports)到fs,然后透過fs的屬性使用各個功能,例如:fs.Stats = binding.Stats。

由于fs參考exports并且它是一個物件,所以當我們require('fs')時,我們就能夠透過屬性使用那些功能。

  1. fs.readFile = function(path,options,callback_){ 
  2.  //… 
  3.  

其他東西也是一樣的作法,例如導出構造函數(shù) 

  1. fs.ReadStream = ReadStream 
  2. function ReadStream(path,options){ 
  3.  //… 
  4. ReadStream.prototype.open = function(){ 
  5.  //… 
  6.  

當導出命名空間時,您可以指定屬性到exports,就像fs的作法,又或者可以建立一個新的物件指派給module.exports 

  1. /* exports作法*/ 
  2. exports.verstion = '1.0' 
  3.  
  4. /*或者module.exports作法*/ 
  5. module.exports = { 
  6.  version: '1.0', 
  7.  doYourTasks: function(){ 
  8.  //… 
  9.  } 
  10.  

一個常見的作法就是透過一個根模塊(root)來匯整并導出其他模塊,如此一來只需要一個require便可以使用所有的模塊。

原文作者在Good Eggs工作時,會將數(shù)據(jù)模型(Model)拆分成個別的模塊,并使用導出構造函數(shù)的方式導出(請參考下文介紹),然后透過一個index檔案來集合該目錄下所有的數(shù)據(jù)模型并一起導出,如此一來在models命名空間下的所有數(shù)據(jù)模型都可以使用 

  1. var models = require('./models') 
  2. var User = models.User 
  3. var Product = models.Product  

在ES2015和CoffeeScript中我們甚至還可以使用解構指派來匯入我們需要的功能 

  1. /* CoffeeScript */ 
  2. {User,Product} = require './models' 
  3.  
  4. /* ES2015 */ 
  5. import {User,Product} from './models'  

而剛剛提到的index.js大概就如下

  1. exports.User = require('./User') 
  2. exports.Person = require('./person')  

實際上這樣分開的寫法還有更精簡的寫法,我們可以透過一個小小的函式庫來匯入在同一階層中所有檔案并搭配CamelCase的命名規(guī)則導出。

于是在我們的index.js中看起來就會如下 

  1. module.exports = require('../lib/require_siblings')(__filename) 

導出函式

另外一個設計模式是導出函式當作該模塊的界面。常見的作法是導出一個工廠函式(Factory function),然后呼叫并回傳一個物件。

在使用Express.js的時候便是這種作法

  1. var express = require('express') 
  2. var app = express() 
  3.  
  4. app.get('/hello'function(req,res,next){ 
  5.  res.send('Hi there!We are using Express v' + express.version) 
  6. })  

Express導出該函式,讓我們可以用來建立一個新的express應用程序。

在使用這種模式時,通常我們會使用factory function搭配參數(shù)讓我們可以設定并回傳初始化后的物件。

想要導出function,我們就一定要使用module.exports,Express便是這么做 

  1. exports = module.exports = createApplication  
  2. … 
  3. function createApplication(){ 
  4. … 
  5.  

上面指派了createApplication函式到module.exports然后再指給exports確保參考一致。

同時Express也使用下面這種方式將導出函式當作命名空間的作法使用。 

  1. exports.version = '3.1.1' 

這邊要大略解釋一下由于Javascript原生并沒有支持命名空間的機制,于是大部分在JS中提到的namespace指的就是透過物件封裝的方式來達到namespace的效果,也就是***種設計模式。

注意!并沒有任何方式可以阻止我們將導出的函式作為命名空間物件使用,我們可以用其來引用其他的function,構造函數(shù),物件。

Express 3.3.2 / 2013-07-03之后已經將exports.version移除了

另外在導出函式的時候***為其命名,如此一來當出錯的時候我們比較容易從錯誤堆疊信息中找到問題點。

下面是兩個簡單的例子: 

  1. /* bomb1.js */ 
  2. module.exports = function(){ 
  3.  throw new Error('boom') 
  4. module.exports = function bomb(){ 
  5.  throw new Error('boom') 
  6. $ node 
  7. > bomb = require('./bomb1'); 
  8. [Function
  9. > bomb() 
  10. Error: boom 
  11.  at module.exports(/Users/andyyou/Projects/export_this/bomb1.js:2:9) 
  12.  at repl:1:2 
  13. … 
  14. > bomb = require('./bomb2'); 
  15. [Function: bomb] 
  16. > bomb() 
  17. Error: boom  
  18.  at bomb(/Users/andyyou/Projects/export_this/bomb2.js:2:9) 
  19.  at repl:1:2 
  20. …  

導出函式還有些比較特別的案例,值得用另外的名稱以區(qū)分它們的不同。

導出高階函式

一個高階函式或functor基本上就是一個函式可以接受一個或多個函式為其輸入或輸出。而這邊我們要談論的后者-一個函式回傳函式

當我們想要模塊能夠根據(jù)輸入控制回傳函式的行為時,導出一個高階函式就是一種非常實用的設計模式。

補充:functor & monad

舉例來說Connect就提供了許多可掛載的功能給網頁框架。

這里的middleware我們先理解成一個有三個參數(shù)(req,res,next)的function。

Express從v4.x版之后不再相依于connect

connect middleware慣例就是導出的function執(zhí)行后,要回傳一個middleware function。

在處理request的過程中這個回傳的middleware function就可以接手使用剛剛提到的三個參數(shù),用來在過程中做一些處理或設定。

同時因為閉包的特性這些設定在整個中間件的處理流程中都是有效的。

舉例來說,compression這個middleware就可以在處理responsive過程中協(xié)助壓縮 

  1. var connect = require('connect') 
  2. var app = connect() 
  3.  
  4. // gzip outgoing responses 
  5. var compression = require('compression') 
  6. app.use(compression())  

而它的原始碼看起來就如下 

  1. module.exports = compression 
  2. … 
  3. function compression(options){ 
  4. … 
  5.  return function compression(req,res,next){ 
  6. … 
  7.  next() 
  8.  } 
  9.  

于是每一個request都會經過compression middleware處理,而代入的options也因為閉包的關系會被保留下來

這是一種***彈性的模塊作法,也可能在您的開發(fā)項目上幫上許多忙。

middleware在這里您可以大略想成串連執(zhí)行一系列的function,自然其Function Signature要一致

導出構造函數(shù)

在一般面向對象語言中,constructor構造函數(shù)指的是一小段代碼協(xié)助我們從類別Class建立一個物件。 

  1. // C# 
  2. class Car { 
  3.  // c#構造函數(shù) 
  4.  // constructor即class中用來初始化物件的method。 
  5.  public Car(name){ 
  6.  name = name; 
  7.  } 
  8. var car = new Car('BMW');  

由于在ES2015之前Javascript并不支持類別,某種程度上在Javascript之中我們可以把任何一個function當作類別,或者說一個function可以當作function執(zhí)行或者搭配new關鍵字當作constructor來使用。如果想知道更詳細的介紹可以閱讀MDN教學。

欲導出構造函數(shù),我們需要透過構造函式來定義類別,然后透過new來建立物件實例。 

  1. function Person(name){ 
  2.  this.name = name 
  3.  
  4. Person.prototype.greet = function(){ 
  5.  return 'Hi,I am ' + this.name 
  6.  
  7. var person = new Person('andyyou') 
  8. console.log(person.greet())// Hi,I am andyyou  

在這種設計模式底下,我們通常會將每個檔案設計成一個類別,然后導出構造函數(shù)。這使得我們的項目構架更加清楚。 

  1. var Person = require('./person') 
  2. var person = new Person()  

整個檔案看起來會如下 

  1. /* person.js */ 
  2. function Person(name){ 
  3.  this.name = name 
  4.  
  5. Person.prototype.greet = function(){ 
  6.  return 'Hi,I am ' + this.name 
  7.  
  8. exports = module.exports = Person  

導出單一物件實例Signleton

當我們需要所有的模塊使用者共享物件的狀態(tài)與行為時,就需要導出單一物件實例。

Mongoose是一個ODM(Object-Document Mapper)函式庫,讓我們可以使用程序中的Model物件去操作MongoDB。 

  1. var mongoose = require('mongoose') 
  2. mongoose.connect'mongodb://localhost/test') 
  3.  
  4. var Cat = mongoose.model('Cat',{name: String}) 
  5.  
  6. var kitty = new Cat({name'Zildjian'}) 
  7. kitty.save(function(err){ 
  8.  if(err) 
  9.  throw Error('save failed') 
  10.  console.log('meow') 
  11. }) 

 那我們require取得的mongoose物件是什么東西呢?事實上mongoose模塊的內部是這么處理的 

  1. function Mongoose(){ 
  2. … 
  3.  
  4. module.exports = exports = new Mongoose()  

因為require的緩存了module.exports的值,于是所有reqire('mongoose')將會回傳相同的物件實例,之后在整個應用程序之中使用的都會是同一個物件。

Mongoose使用面向對象的設計模式來封裝,解耦(分離功能之間的相依性),維護狀態(tài)使整體具備可讀性,同時透過導出一個Mongoose Class的物件給使用者,讓我們可以簡單的存取使用。

如果我們有需要,它也可以建立其他的物件實例來作為命名空間使用。實際上Mongoose內部提供了存取構造函數(shù)的方法 

  1. Mongoose.prototype.Mongoose = Mongoose 

因此我們可以這么做 

  1. var mongoose = require('mongoose'
  2.  
  3. var Mongoose = mongoose.Mongoose 
  4.  
  5. var anotherMongoose = new Mongoose() 
  6.  
  7. anotherMongoose.connect('mongodb://localhost/test' 

擴展全局物件

一個被匯入的模塊不只限于單純取得其導出的數(shù)據(jù)。它也可以用來修改全局物件或回傳全局物件,自然也能定義新的全局物件。而在這邊的全局物件(Global objects)或稱為標準內置物件像是Object,F(xiàn)unction,Array指的是在全局能存取到的物件們,而不是當Javascript開始執(zhí)行時所產生代表global scope的global object。

當我們需要擴增或修改全局物件預設行為時就需要使用這種設計模式。當然這樣的方式是有爭議,您必須謹慎使用,特別是在開放原始碼的項目上。

例如:Should.js是一個常被用在單元測試中用來判斷分析值是否正確的函式庫。 

  1. require('should') 
  2.  
  3. var user = { 
  4.  name'andyyou' 
  5.  
  6. user.name.should.equal('andyyou')  

這樣您是否比較清楚了,should.js增加了底層的Object的功能,加入了一個非列舉型的屬性 should,讓我們可以用簡潔的語法來撰寫單元測試。

而在內部should.js做了這樣的事情 

  1. var should = function(obj){ 
  2.  return new Assertion(util.isWrapperType(obj)?obj.valueOf():obj) 
  3. … 
  4. exports = module.exports = should 
  5.  
  6. Object.defineProperty(Object.prototype,'should',{ 
  7.  setfunction(){}, 
  8.  get: function(){ 
  9.  return should(this); 
  10.  }, 
  11.  configurable: true 
  12. });  

就算看到這邊您肯定跟我一樣有滿滿的疑惑,全局物件擴展定義跟exprots有啥關聯(lián)呢?

事實上 

  1. /* whoami.js */ 
  2. exports = module.exports = { 
  3.  name'andyyou' 
  4.  
  5. Object.defineProperty(Object.prototype,'whoami',{ 
  6.  setfunction(){}, 
  7.  get: function(){ 
  8.  return 'I am ' + this.name 
  9.  } 
  10. }) 
  11.  
  12. /* app.js */ 
  13. var whoami = require('whoami') 
  14. console.log(whoami)// { name'andyyou' } 
  15.  
  16. var obj = { name'lena' } 
  17. console.log(obj.whoami)// I am lena  

現(xiàn)在我們明白了上面說的修改全局物件的意思了。should.js導出了一個should函式但是它主要的使用方式則是把should加到Object屬性上,透過物件本身來呼叫。

套用猴子補丁(Monkey Patch)

在這邊所謂的猴子補丁特別指的是在執(zhí)行時期動態(tài)修改一個類別或者模塊,通常會這么做是希望補強某的第三方套件的bug或功能。

假設某個模塊沒有提供您客制化功能的界面,而您又需要這個功能的時候,我們就會實作一個模塊來補強既有的模塊。

這個設計模式有點類似擴展全局物件,但并非修改全局物件,而是依靠Node模塊系統(tǒng)的緩存機制,當其他代碼匯入該模塊時去補強該模塊的實例物件。

預設來說Mongoose會使用小寫以及復數(shù)的慣例替數(shù)據(jù)模型命名。例如一個數(shù)據(jù)模型叫做CreditCard最終我們會得到collection的名稱是creditcards。假如我們希望可以換成credit_cards并且其他地方也遵循一樣的用法。

下面是我們試著使用猴子補丁的方式來替既有的模塊增加功能 

  1. var pluralize = require('pluralize')//處理復數(shù)單字的函式庫 
  2. var mongoose = require('mongoose') 
  3. var Mongoose = mongoose.Mongoose 
  4.  
  5. mongoose.Promise = global.Promise // v4.1+ http://mongoosejs.com/docs/promises.html 
  6. var model = Mongoose.prototype.model 
  7.  
  8. //補丁 
  9. var fn = functionnameschema,collection,skipInit){ 
  10.  collection = collection || pluralize.plural(name.replace(/([a-z\d])([A-Z])/g,'$1_$2').toLowerCase()) 
  11.  return model.call(this,nameschema,collection,skipInit) 
  12. Mongoose.prototype.model = fn 
  13.  
  14. /*實際測試*/ 
  15. mongoose.connect'mongodb://localhost/test') 
  16. var CreditCardSchema = new mongoose.Schema({number: String}) 
  17. var CreditCardModel = mongoose.model('CreditCard',CreditCardSchema); 
  18.  
  19. var card = new CreditCardModel({number: '5555444433332222'}); 
  20. card.save(function(err){ 
  21.  if(err){ 
  22.  console.log(err) 
  23.  } 
  24.  console.log('success') 
  25. })  

您不該輕易使用上面這種方式補丁,這邊只是為了說明猴子補丁這種方式,mongoose已經有提供官方的方式設定名稱 

  1. var schema = new Schema({..},{ collection: 'your_collection_name' }) 

當這個模塊***次被匯入的時候便會讓mongoose重新定義Mongoose.prototype.model并將其設回原本的model的實作。

如此一來所有Mongoose的實例物件都具備新的行為了。注意到這邊并沒有修改exports所以當我們require的時候得到的是預設的物件

另外如果您想使用上面這種補丁的方式時,記得閱讀原始碼并注意是否產生沖突。

請善用導出的功能

Node模塊系統(tǒng)提供了一個簡單的機制來封裝功能,使我們能夠建立了清楚的界面。希望掌握這七種設計模式提供不同的優(yōu)缺點能對您有所幫助。

在這邊作者并沒有徹底的調查所有的方式,一定有其他選項可供選擇,這邊只有描述幾個最常見且不錯的方法。

小結

  • namespace:導出一個物件包含需要的功能

root module的方式,使用一個根模塊導出其他模塊

  • function:直接將module.exports設為function

Function物件也可以拿來當作命名空間使用

為其命名方便調試

exports = module.exports = something的作法是為了確保參考(Reference)一致

  • high-order function:可以透過代入?yún)?shù)控制并回傳function。

可協(xié)助實作middleware的設計模式

換句話說middleware即一系列相同signature的function串連。一個接一個執(zhí)行

  • constructor:導出類別(function),使用時再new,具備OOP的優(yōu)點
  • singleton:導出單一物件實例,重點在各個檔案可以共享物件狀態(tài)
  • global objects:在全局物件作的修改也會一起被導出
  • monkey patch:執(zhí)行時期,利用Node緩存機制在instance加上補丁

筆記

  • 一個javascript檔案可視為一個模塊
  • 解決特定問題或需求,功能完整由單一或多個模塊組合而成的整體稱為套件(package)
  • require匯入的模塊具有自己的scope
  • exports只是module.exports的參考,exports會記錄收集屬性如果module.exports沒有任何屬性就把其數(shù)據(jù)交給module.exports,但如果module.exports已經具備屬性的話,那么exports的所有數(shù)據(jù)都會被忽略。
  • 就算exports置于后方仍會被忽略
  • Node初始化的順序

Native Module -> Module

StartNodeInstance()-> CreateEnvironment()-> LoadEnvironment()-> Cached

  • Native Module加載機制

檢查是否有緩存

->有;直接回傳this.exports

->沒有;new一個模塊物件

cache()

compile()-> NativeModule.wrap()將原始碼包進function字串->runInThisContext()建立函式

return NativeModule.exports

  • Node的require會cache,也就是說:如果希望模塊產生不同的instance時應使用function
責任編輯:龐桂玉 來源: segmentfault
相關推薦

2021-02-19 14:07:03

JavaScript編程開發(fā)

2009-06-29 18:11:40

JSP設計模式

2009-01-04 13:49:17

Java設計模式設計模式工廠模式

2023-09-22 11:58:49

2020-10-14 13:58:14

23種設計模式速記

2015-09-14 09:31:44

結對設計

2020-10-09 06:52:31

設計模式軟件

2022-05-27 11:33:02

前端代碼設計模式

2009-06-15 14:15:07

Java設計模式Java

2023-05-15 15:29:13

設計模式JavaScript

2021-04-09 20:38:20

Vue模式.前端

2018-08-29 10:04:43

2012-08-30 09:07:33

設計模式

2021-04-18 21:07:32

門面模式設計

2024-07-31 08:12:33

2023-10-26 09:02:30

框架設計模式

2020-11-08 16:04:03

開發(fā)工具技術

2020-11-18 08:15:39

TypeScript設計模式

2012-04-10 10:04:26

并行編程

2012-12-25 09:38:41

JavaScript設計模式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一级在线免费观看 | 亚洲精品乱码久久久久久按摩观 | 成人国内精品久久久久一区 | 人妖无码 | 久久精品一级 | 你懂的国产 | 99福利视频 | 99久久婷婷国产综合精品电影 | 夜夜爽99久久国产综合精品女不卡 | 91精品久久久久久久久久 | 中文字幕一区二区三区乱码在线 | 精品一二区 | 日韩欧美在线视频播放 | 精品久久久久久亚洲综合网 | 在线国产视频 | 国产一区二区在线视频 | 国产成人免费视频网站高清观看视频 | 日日干日日色 | 国产精品亚洲综合 | 黄色毛片在线播放 | 久久99精品久久久久蜜桃tv | 中文字幕一区二区三区日韩精品 | 一区二区在线免费观看视频 | 亚洲性视频 | 免费激情网站 | 一区二区在线 | 在线中文字幕亚洲 | 中文在线一区二区 | 99久久久无码国产精品 | 免费特黄视频 | 久热久 | 视频一区二区三区四区五区 | 国产精品永久在线观看 | 中文字幕日韩一区 | 亚洲精品一 | 日韩中字幕 | 青青久在线视频 | 国产精品免费看 | 7777精品伊人久久精品影视 | 国产视频一区二区 | 欧美狠狠操 |