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

如果面試官讓你講講發布訂閱設計模式?

開發 前端
發布訂閱設計模式在程序中經常涉及,例如 Vue 中的 $on 和 $off、document.addEventListener()、document.removeEventListener()等,發布訂閱模式可以降低程序的耦合度,統一管理維護消息、處理事件也使得程序更容易維護和擴展。

[[414875]]

本文轉載自微信公眾號「DYBOY」,作者DYBOY。轉載本文請聯系DYBOY公眾號。

發布訂閱設計模式在程序中經常涉及,例如 Vue 中的 $on 和 $off、document.addEventListener()、document.removeEventListener()等,發布訂閱模式可以降低程序的耦合度,統一管理維護消息、處理事件也使得程序更容易維護和擴展。

有小伙伴問,該如何學習設計模式,設計模式本身是一些問題場景的抽象解決方案,死記硬背肯定不行,無異于搭建空中樓閣,所以得結合實際,從解決問題角度去思考、舉一反三,如此便能更輕松掌握知識點。

最近在程序中使用到了 eventEmitter3 這個事件發布訂閱庫,該庫可用于組件之間的通信管理,通過簡單的 Readme 文檔可學會如何使用,但同時了解這個庫的設計也有助于大家了解認識發布訂閱設計模式,不妨一起來看看。

一、定義

在軟件架構中,發布訂閱是一種消息范式,消息的發送者(稱為發布者)不會將消息直接發送給特定的接收者(稱為訂閱者),而是將發布的消息分為不同的類別,無需了解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需了解哪些發布者(如果有的話)存在。

類比一個很好理解的例子,例如微信公眾號,你關注(理解為訂閱)了“DYBOY”公眾號,當該公眾號發布了新文章,微信就會通知你,而不會通知其他為訂閱公眾號的人,另外你還可以訂閱多個公眾號。

放到程序的組件中,多個組件的通信除了父子組件傳值外,還有例如 redux、vuex 狀態管理,另外就是本文所說的發布訂閱模式,可以通過一個事件中心來實現。

發布訂閱模式

二、手搓一個發布訂閱事件中心

“紙上得來終覺淺,絕知此事要躬行”,所以根據定義,我們嘗試實現一個JavaScript版本的發布訂閱事件中心,看看會遇到哪些問題?

2.1 基本結構版

首先實現的 DiyEventEmitter 如下:

  1. /** 
  2.  * 事件發布訂閱中心 
  3.  */ 
  4. class DiyEventEmitter { 
  5.   static instance: DiyEventEmitter; 
  6.   private _eventsMap: Map<string, Array<() => void>>; 
  7.  
  8.   static getInstance() { 
  9.     if (!DiyEventEmitter.instance) { 
  10.       DiyEventEmitter.instance = new DiyEventEmitter(); 
  11.     } 
  12.     return DiyEventEmitter.instance; 
  13.   } 
  14.  
  15.   constructor() { 
  16.     this._eventsMap = new Map(); // 事件名與回調函數的映射Map 
  17.   } 
  18.  
  19.   /** 
  20.    * 事件訂閱 
  21.    * 
  22.    * @param eventName 事件名 
  23.    * @param eventFnCallback 事件發生時的回調函數 
  24.    */ 
  25.   public on(eventName: string, eventFnCallback: () => void) { 
  26.     const newArr = this._eventsMap.get(eventName) || []; 
  27.     newArr.push(eventFnCallback); 
  28.     this._eventsMap.set(eventName, newArr); 
  29.   } 
  30.  
  31.   /** 
  32.    * 取消訂閱 
  33.    * 
  34.    * @param eventName 事件名 
  35.    * @param eventFnCallback 事件發生時的回調函數 
  36.    */ 
  37.   public off(eventName: string, eventFnCallback?: () => void) { 
  38.     if (!eventFnCallback) { 
  39.       this._eventsMap.delete(eventName); 
  40.       return
  41.     } 
  42.  
  43.     const newArr = this._eventsMap.get(eventName) || []; 
  44.     for (let i = newArr.length - 1; i >= 0; i--) { 
  45.       if (newArr[i] === eventFnCallback) { 
  46.         newArr.splice(i, 1); 
  47.       } 
  48.     } 
  49.     this._eventsMap.set(eventName, newArr); 
  50.   } 
  51.  
  52.   /** 
  53.    * 主動通知并執行注冊的回調函數 
  54.    * 
  55.    * @param eventName 事件名 
  56.    */ 
  57.   public emit(eventName: string) { 
  58.     const fns = this._eventsMap.get(eventName) || []; 
  59.     fns.forEach(fn => fn()); 
  60.   } 
  61.  
  62. export default DiyEventEmitter.getInstance(); 

導出的 DiyEventEmitter 是一個“單例”,保證在全局中只有唯一“事件中心”實例,使用時候直接可使用公共方法

  1. import e from "./DiyEventEmitter"
  2.  
  3. const subscribeFn = () => { 
  4.   console.log("DYBOY訂閱收到了消息"); 
  5. }; 
  6. const subscribeFn2 = () => { 
  7.   console.log("DYBOY第二個訂閱收到了消息"); 
  8. }; 
  9.  
  10. // 訂閱 
  11. e.on("dyboy", subscribeFn); 
  12. e.on("dyboy", subscribeFn2); 
  13.  
  14. // 發布消息 
  15. e.emit("dyboy"); 
  16.  
  17. // 取消第一個訂閱消息的綁定 
  18. e.off("dyboy", subscribeFn); 
  19.  
  20. // 第二次發布消息 
  21. e.emit("dyboy"); 

輸出 console 結果:

  1. DYBOY訂閱收到了消息 
  2. 第二個訂閱的消息 
  3. 第二個訂閱的消息 

那么第一版的支持訂閱、發布、取消的“發布訂閱事件中心”就OK了。

2.2 支持只訂閱一次once方法

在一些場景下,某些事件訂閱可能只需要執行一次,后續的通知將不再響應。

實現的思路:新增 once 訂閱方法,當響應了對應“發布者消息”,則主動取消訂閱當前執行的回調函數。

為此新增類型,如此便于回調函數的描述信息擴展:

  1. type SingleEvent = { 
  2.   fn: () => void; 
  3.   once: boolean; 
  4. }; 

_eventsMap的類型更改為:

  1. private _eventsMap: Map<string, Array<SingleEvent>>; 

同時抽出公共方法 addListener,供 on 和 once 方法共用:

  1. private addListener( eventName: string, eventFnCallback: () => void, once = false) { 
  2.   const newArr = this._eventsMap.get(eventName) || []; 
  3.   newArr.push({ 
  4.     fn: eventFnCallback, 
  5.     once, 
  6.   }); 
  7.   this._eventsMap.set(eventName, newArr); 
  8.  
  9. /** 
  10.  * 事件訂閱 
  11.  * 
  12.  * @param eventName 事件名 
  13.  * @param eventFnCallback 事件發生時的回調函數 
  14.  */ 
  15. public on(eventName: string, eventFnCallback: () => void) { 
  16.   this.addListener(eventName, eventFnCallback); 
  17.  
  18. /** 
  19.  * 事件訂閱一次 
  20.  * 
  21.  * @param eventName 事件名 
  22.  * @param eventFnCallback 事件發生時的回調函數 
  23.  */ 
  24. public once(eventName: string, eventFnCallback: () => void) { 
  25.   this.addListener(eventName, eventFnCallback, true); 

與此同時,我們需要考慮在觸發事件時候,執行一次就需要取消訂閱

  1. /** 
  2.  * 觸發:主動通知并執行注冊的回調函數 
  3.  * 
  4.  * @param eventName 事件名 
  5.  */ 
  6. public emit(eventName: string) { 
  7.   const fns = this._eventsMap.get(eventName) || []; 
  8.   fns.forEach((evt, index) => { 
  9.     evt.fn(); 
  10.     if (evt.once) fns.splice(index, 1); 
  11.   }); 
  12.   this._eventsMap.set(eventName, fns); 

另外取消訂閱中函數中比較需要替換對象屬性比較:newArr[i].fn === eventFnCallback

這樣我們的事件中心支持 once 方法改造就完成了。

2.3 緩存發布消息

在框架開發下,通常會使用異步按需加載組件,如果發布者組件先發布了消息,但是異步組件還未加載完成(完成訂閱注冊),那么發布者的這條發布消息就不會被響應。因此,我們需要把消息做一個緩存隊列,直到有訂閱者訂閱了,并只響應一次緩存的發布消息,該消息就會從緩存出隊。

首先梳理下緩存消息的邏輯流程:

UML時序圖

發布者發布消息,事件中心檢測是否存在訂閱者,如果沒有訂閱者訂閱此條消息,則把該消息緩存到離線消息隊列中,當有訂閱者訂閱時,檢測是否訂閱了緩存中的事件消息,如果是,則該事件的緩存消息依次出隊(FCFS調度執行),觸發訂閱者回調函數執行一次。

新增離線消息緩存隊列:

  1. private _offlineMessageQueue: Map<string, number>; 

在emit發布消息中判斷對應事件是否有訂閱者,沒有訂閱者則向離線事件消息中更新

  1. /** 
  2.  * 觸發:主動通知并執行注冊的回調函數 
  3.  * 
  4.  * @param eventName 事件名 
  5.  */ 
  6. public emit(eventName: string) { 
  7.   const fns = this._eventsMap.get(eventName) || []; 
  8. +  if (fns.length === 0) { 
  9. +    const counter = this._offlineMessageQueue.get(eventName) || 0; 
  10. +    this._offlineMessageQueue.set(eventName, counter + 1); 
  11. +    return
  12. +  } 
  13.   fns.forEach((evt, index) => { 
  14.     evt.fn(); 
  15.     if (evt.once) fns.splice(index, 1); 
  16.   }); 
  17.   this._eventsMap.set(eventName, fns); 

然后在 addListener 方法中根據離線事件消息統計的次數,重新emit發布事件消息,觸發消息回調函數執行,之后刪掉離線消息中的對應事件。

  1. private addListener( 
  2.   eventName: string, 
  3.   eventFnCallback: () => void, 
  4.   once = false 
  5. ) { 
  6.   const newArr = this._eventsMap.get(eventName) || []; 
  7.   newArr.push({ 
  8.     fn: eventFnCallback, 
  9.     once, 
  10.   }); 
  11.   this._eventsMap.set(eventName, newArr); 
  12.  
  13. +  const cacheMessageCounter = this._offlineMessageQueue.get(eventName); 
  14. +  if (cacheMessageCounter) { 
  15. +    for (let i = 0; i < cacheMessageCounter; i++) { 
  16. +      this.emit(eventName); 
  17. +    } 
  18. +    this._offlineMessageQueue.delete(eventName); 
  19. +  } 

這樣,一個支持離線消息的事件中心就寫好了!

2.4 回調函數傳參&執行環境

在上面的回調函數中,我們可以發現是一個沒有返回值,沒有入參的函數,這其實有些雞肋,在函數運行的時候會指向執行的上下文,可能某些回調函數中含有this指向就無法綁定到事件中心上,因此針對回調函數需要綁定執行上下文環境。

2.4.1 支持回調函數傳參

首先將TypeScript中的函數類型fn: () => void 改為 fn: Function,這樣能夠通過函數任意參數長度的TS校驗。

其實在事件中心里回調函數是沒有參數的,如有參數也是提前通過參數綁定(bind)方式傳入。

另外如果真要支持回調函數傳參,那么就需要在 emit() 的時候傳入參數,然后再將參數傳遞給回調函數,這里我們暫時先不實現了。

2.4.2 執行環境綁定

在需要實現執行環境綁定這個功能前,先思考一個問題:“是應該開發者自行綁定還是應該事件中心來做?”

換句話說,開發者在 on('eventName', 回調函數) 的時候,是否應該主動綁定 this 指向?在當前設計下,初步認為無參數的回調函數自行綁定 this 比較合適。

因此,在事件中心這暫時不需要去做綁定參數的行為,如果回調函數內有需要傳參、綁定執行上下文的,需要在綁定回調函數的時候自行 bind。這樣,我們的事件中心也算是保證了功能的純凈性。

到這里我們自己手搓簡單的發布訂閱事件中心就完成了!

三、學習EventEmitter3的設計實現

雖然我們按照自己的理解實現了一版,但是沒有對比我們也不知道好壞,因此一起看看 EventEmitter3 這個優秀“極致性能優化”的庫是怎么去處理事件訂閱與發布,同時可以學習下其中的性能優化思路。

首先,EventEmitter3(后續簡稱:EE3)的實現思路,用Events對象作為“回調事件對象”的存儲器,類比我們上述實現的“發布訂閱模式”作為事件的執行邏輯,另外addListener() 函數增加了傳入執行上下文環境參數,emit() 函數支持最多傳入5個參數,同時EventEmitter3中還加入了監聽器計數、事件名前綴。

3.1 Events存儲器

避免轉譯,以及為了提升兼容性和性能,EventEmitter3用ES5來編寫。

在JavaScript中萬物是對象,函數也是對象,因此存儲器的實現:

  1. function Events() {} 

3.2 事件偵聽器實例

同理,我們上述使用singleEvent對象來存儲每一個事件偵聽器實例,EE3 中用一個EE對象存儲每個事件偵聽器的實例以及必要屬性

  1. /** 
  2.  * 每個事件偵聽器實例的表示形式 
  3.  * 
  4.  * @param {Function} fn 偵聽器函數 
  5.  * @param {*} context 調用偵聽器的執行上下文 
  6.  * @param {Boolean} [once=false] 指定偵聽器是否僅支持調用一次 
  7.  * @constructor 
  8.  * @private 
  9.  */ 
  10. function EE(fn, context, once) { 
  11.   this.fn = fn; 
  12.   this.context = context; 
  13.   this.once = once || false

3.3 添加偵聽器方法

  1. /** 
  2.  * 為給定事件添加偵聽器 
  3.  * 
  4.  * @param {EventEmitter} emitter EventEmitter實例的引用. 
  5.  * @param {(String|Symbol)} event 事件名. 
  6.  * @param {Function} fn 偵聽器函數. 
  7.  * @param {*} context 調用偵聽器的上下文. 
  8.  * @param {Boolean} once 指定偵聽器是否僅支持調用一次. 
  9.  * @returns {EventEmitter} 
  10.  * @private 
  11.  */ 
  12. function addListener(emitter, event, fn, context, once) { 
  13.   if (typeof fn !== 'function') { 
  14.     throw new TypeError('The listener must be a function'); 
  15.   } 
  16.  
  17.   var listener = new EE(fn, context || emitter, once) 
  18.     , evt = prefix ? prefix + event : event; 
  19.  
  20.   // TODO: 這里為什么先是使用對象,多個的時候使用對象數組存儲,有什么好處? 
  21.   if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; 
  22.   else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); 
  23.   else emitter._events[evt] = [emitter._events[evt], listener]; 
  24.  
  25.   return emitter; 

該“添加偵聽器”的方法有幾個關鍵功能點:

如果有前綴,給事件名增加前綴,避免事件沖突

每次新增事件名則 _eventsCount+1,用于快速讀寫所有事件的數量

如果事件只有單個偵聽器,則 _events[evt] 指向這個 EE 對象,訪問效率更高

3.4 清除事件

  1. /** 
  2.  * 通過事件名清除事件 
  3.  * 
  4.  * @param {EventEmitter} emitter EventEmitter實例的引用 
  5.  * @param {(String|Symbol)} evt 事件名 
  6.  * @private 
  7.  */ 
  8. function clearEvent(emitter, evt) { 
  9.   if (--emitter._eventsCount === 0) emitter._events = new Events(); 
  10.   else delete emitter._events[evt]; 

清除事件,只需要使用 delete 關鍵字,刪除對象上的屬性

另外這里一個很巧妙的地方在于,依賴事件計數器,如果計數器為0,則重新創建一個 Events 存儲器指向 emitter 的 _events 屬性。

這樣做的優點是,假如需要清空所有事件,只需要將 emitter._eventsCount 的值賦值為1,然后調用 clearEvent() 方法就可以了,而不必遍歷清除事件

3.5 EventEmitter

  1. function EventEmitter() { 
  2.   this._events = new Events(); 
  3.   this._eventsCount = 0; 

EventEmitter 對象參考 NodeJS 中的事件觸發器,定義了最小的接口模型,包含 _events 和 _eventsCount屬性,另外的方法都通過原型來增加。

EventEmitter 對象等同于上述我們的事件中心的定義,其功能梳理如下:

EventEmitter

其中有必要講的就是 emit() 方法,而訂閱者注冊事件的on() 和 once() 方法,都是使用的 addListener() 工具函數。

emit() 方法實現如下:

  1. /** 
  2.  * 調用執行指定事件名的每一個偵聽器 
  3.  * 
  4.  * @param {(String|Symbol)} event 事件名. 
  5.  * @returns {Boolean} `true` 如果當前事件名沒綁定偵聽器,則返回false
  6.  * @public 
  7.  */ 
  8. EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { 
  9.   var evt = prefix ? prefix + event : event; 
  10.  
  11.   if (!this._events[evt]) return false
  12.  
  13.   var listeners = this._events[evt] 
  14.     , len = arguments.length 
  15.     , args 
  16.     , i; 
  17.  
  18.   // 如果只有一個偵聽器綁定了該事件名 
  19.   if (listeners.fn) { 
  20.     // 如果是執行一次的,則移除偵聽器 
  21.     if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); 
  22.      
  23.     // Refrence:https://juejin.cn/post/6844903496450310157 
  24.     // 這里的處理是從性能上考慮,傳入5個入參數的調用call方法處理 
  25.     // 超過5個參數的使用apply處理 
  26.     // 大部分場景超過5個參數的都是少數 
  27.     switch (len) { 
  28.       case 1: return listeners.fn.call(listeners.context), true
  29.       case 2: return listeners.fn.call(listeners.context, a1), true
  30.       case 3: return listeners.fn.call(listeners.context, a1, a2), true
  31.       case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true
  32.       case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true
  33.       case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true
  34.     } 
  35.  
  36.     for (i = 1, args = new Array(len -1); i < len; i++) { 
  37.       args[i - 1] = arguments[i]; 
  38.     } 
  39.  
  40.     listeners.fn.apply(listeners.context, args); 
  41.   } else { 
  42.     // 當有多個偵聽器綁定了同一個事件名 
  43.     var length = listeners.length 
  44.       , j; 
  45.      
  46.     // 循環執行每一個綁定的事件偵聽器 
  47.     for (i = 0; i < length; i++) { 
  48.       if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); 
  49.  
  50.       switch (len) { 
  51.         case 1: listeners[i].fn.call(listeners[i].context); break; 
  52.         case 2: listeners[i].fn.call(listeners[i].context, a1); break; 
  53.         case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; 
  54.         case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; 
  55.         default
  56.           if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { 
  57.             args[j - 1] = arguments[j]; 
  58.           } 
  59.           listeners[i].fn.apply(listeners[i].context, args); 
  60.       } 
  61.     } 
  62.   } 
  63.  
  64.   return true
  65. }; 

在 emit() 方法中顯示的傳入了五個入參:a1 ~ a5,同時優先使用 call() 方法綁定 this 指向并執行偵聽器的回調函數。

這樣處理的原因是,call 方法比 apply 方法效率更高,相關比較驗證討論可參考《call和apply的性能對比》

到這基本上 EventEmitter3 的實現就啃完了!

四、總結

EventEmitter3 是一個號稱優化到極致的事件發布訂閱的工具庫,通過梳理可知曉:

 

  • call 與 apply 在效率上的差異
  • 對象和對象數組的存取性能考慮
  • 理解發布訂閱模式,以及在事件系統中的應用實例

 

責任編輯:武曉燕 來源: DYBOY
相關推薦

2022-04-29 08:17:38

RPC遠程代理代理模式

2021-11-08 11:32:01

觀察

2020-11-06 07:11:40

內存虛擬Redis

2020-07-28 00:58:20

IP地址子網TCP

2015-08-13 10:29:12

面試面試官

2021-02-28 07:52:24

蠕蟲數據金絲雀

2021-01-14 05:23:32

高并發消息中間件

2020-06-17 21:22:56

Serverless面試官架構

2020-07-03 07:39:45

查詢語句

2020-12-09 05:18:17

面試觀察者訂閱模式

2023-07-13 08:19:30

HaspMapRedis元素

2024-08-16 13:59:00

2021-06-29 11:05:25

MySQLCPU數據庫

2025-10-15 08:06:12

2020-11-02 07:02:10

加載鏈接初始化

2020-10-15 06:26:24

高并發場景冰河

2020-09-07 06:28:37

Nginx靜態負載均衡動態負載均衡

2023-07-11 08:50:34

2024-08-23 11:51:39

2021-10-29 09:40:21

設計模式軟件
點贊
收藏

51CTO技術棧公眾號

国产精品国产自产拍高清av水多| 精品国内二区三区| 一区二区不卡在线观看| 亚洲一级片免费看| 欧美一区亚洲| 精品国产自在久精品国产| 人妻有码中文字幕| 精品孕妇一区二区三区| 国产高清亚洲一区| 欧美在线亚洲一区| 国产精品精品软件男同| 国产伦精品一区二区三区免费优势 | 尤物国产在线观看| 成年人国产在线观看| 99久久伊人久久99| 91免费高清视频| 国产专区第一页| 五月久久久综合一区二区小说| 欧美精品一区二区久久婷婷| 亚洲一区日韩精品| 亚洲天堂一区二区| 亚洲成人免费视| 一区二区成人国产精品| 免费在线看v| 高清国产一区二区三区| 国产又爽又黄的激情精品视频| 久久不卡免费视频| 欧美国产激情| 尤物精品国产第一福利三区| 国产精品果冻传媒| 国产精品久久久久久久久免费高清| 性感美女久久精品| 警花观音坐莲激情销魂小说| 国产日本在线观看| 久久综合久久综合久久| 国产欧美一区二区三区另类精品 | 久久最新免费视频| 午夜伦理在线| 国产欧美一区二区在线| 精品日产一区2区三区黄免费| 国产v在线观看| 国产一区二区三区免费看| 国产精品久久久av| 午夜精品久久久久久久蜜桃| av不卡在线看| 欧美黄色三级网站| 亚洲熟女www一区二区三区| 91视频久久| 色爱av美腿丝袜综合粉嫩av| av无码av天天av天天爽| 开心激情综合| 亚洲精品成人久久| 亚洲男人在线天堂| 天天操综合520| 精品一区二区电影| 中文字幕一区二区三区人妻| 日韩伦理一区二区三区| 亚洲精品一区二区三区不| 91国模少妇一区二区三区| 亚州综合一区| 亚洲香蕉伊综合在人在线视看| 黄色正能量网站| 欧美男同视频网| 自拍偷拍亚洲在线| 天海翼在线视频| 欧美国产三级| 久久久久久91香蕉国产| 精品在线播放视频| 欧美专区在线| 国产精品美女在线| 国产男男gay体育生白袜| 国产高清在线精品| 久久久久久九九| 搞黄视频在线观看| 亚洲美女偷拍久久| 97在线国产视频| 欧美momandson| 欧美色倩网站大全免费| a级大片免费看| 卡通动漫精品一区二区三区| 国产一区av在线| 国产精品嫩草影院俄罗斯| 欧美日本中文| 秋霞av国产精品一区| 中文字幕乱码在线观看| 国产福利不卡视频| 乱一区二区三区在线播放| 在线免费观看黄色av| 一区二区三区在线免费视频| 成人av一级片| 久久人体av| 亚洲国产成人久久综合一区| 欧美熟妇激情一区二区三区| 一区二区三区四区电影| 欧美一级大片在线免费观看| 国产精品成人久久久| 国产高清不卡一区二区| 欧美亚州在线观看| 色www永久免费视频首页在线| 日韩欧美有码在线| 男女视频在线观看网站| 九一国产精品| 欧美日本啪啪无遮挡网站| caoporn国产| 国产精品乡下勾搭老头1| 欧美在线视频一区二区三区| 中文在线字幕免费观看| 91九色最新地址| 黄色性视频网站| 婷婷综合亚洲| 日韩女优在线播放| 动漫av一区二区三区| 国产精品美女久久久久久久久久久| h无码动漫在线观看| 日本精品久久| 亚洲人精选亚洲人成在线| 久久久久久激情| 久久99国产精品麻豆| 欧美日韩一区二区视频在线| 激情av在线播放| 91精品国产91久久久久久一区二区| 中文字幕在线看高清电影| 伊人久久亚洲美女图片| 91精品视频免费| youjizz在线播放| 欧美性生交xxxxxdddd| 亚洲无人区码一码二码三码| 久久久久国产精品| 国产精品视频yy9099| 日本aaa在线观看| 亚洲大尺度视频在线观看| 97人人模人人爽人人澡| 久久密一区二区三区| 国产99在线|中文| 三级在线播放| 天天色综合天天| 99久久久无码国产精品性波多 | 久久一日本道色综合| a级黄色小视频| 99久热这里只有精品视频免费观看| 久久九九亚洲综合| 一本一道精品欧美中文字幕| 日本一区二区三区免费乱视频| 两根大肉大捧一进一出好爽视频| 国产一区调教| 91精品国产91久久久久久久久| 丰满肉嫩西川结衣av| 亚洲国产乱码最新视频 | 久久夜色精品一区| 久草青青在线观看| 最新亚洲精品| 国产精品com| 高清毛片在线看| 欧美日韩中文字幕一区| 国产视频123区| 精品亚洲国产成人av制服丝袜| 一区二区三区四区视频在线观看| 黑人一区二区三区| 久久久精品国产亚洲| 一级黄色片免费| 亚洲欧美日韩国产综合| 天堂av手机在线| 欧美暴力喷水在线| 国产精品永久入口久久久| 国产拍在线视频| 亚洲人成啪啪网站| 中文字幕乱码人妻二区三区| 亚洲欧美另类综合偷拍| 精品人妻一区二区免费| 午夜在线视频观看日韩17c| 日本一区二区三区四区在线观看 | 国产成人无码精品久久二区三| 久久综合婷婷| 一区二区三区四区视频在线观看 | 日韩欧美的一区| 国产又大又黑又粗免费视频| 久久网这里都是精品| 中文字幕av不卡在线| 欧美天堂亚洲电影院在线观看 | 五月婷婷激情综合| 国产特级黄色录像| 国产精品一区二区三区四区| 精品少妇人妻av免费久久洗澡| 国产欧美日韩视频在线| 91九色精品视频| 擼擼色在线看观看免费| 中文字幕欧美在线| 成人小说亚洲一区二区三区| 色老头久久综合| 久久久久久久久久网站| 91一区二区三区在线观看| 自拍偷拍21p| 亚洲人体偷拍| 一本一本久久a久久精品综合妖精| 91综合久久爱com| 国产精品国产自产拍高清av水多 | 69久久精品无码一区二区 | 中文字幕国产免费| 精品白丝av| 亚洲欧美日韩不卡一区二区三区| 99这里只有精品视频| 国产精品久久久久久影视| 9lporm自拍视频区在线| 色悠悠国产精品| 亚洲人在线观看视频| 欧美一区二区三区在线看| 人人爽人人爽人人片av| 亚洲免费av网站| 懂色av蜜桃av| 91蜜桃婷婷狠狠久久综合9色| 亚洲日本黄色片| 久久美女性网| 欧美午夜性视频| 亚洲精彩视频| 亚洲精品日韩在线观看| 蜜桃精品噜噜噜成人av| 成人女人免费毛片| 91精品亚洲一区在线观看| 日本国产一区二区三区| xxxx在线视频| 欧美日本中文字幕| 免费看a在线观看| 一本色道久久综合狠狠躁篇的优点| 黄片毛片在线看| 日韩色视频在线观看| 国产精品九九九九| 欧美丝袜第三区| 无码人妻久久一区二区三区不卡| 午夜影院久久久| 久久久香蕉视频| 一区二区三区在线视频免费观看| 一级免费黄色录像| 国产精品入口麻豆原神| 国产在线综合视频| 久久久久久久久久久久久久久99 | 精品不卡一区| 免费成人在线观看av| 日本三级久久| 国内视频一区| 卡通动漫国产精品| 久久天天狠狠| 视频福利一区| 欧美大陆一区二区| 亚州综合一区| 日韩国产高清一区| 欧美男gay| 视频一区二区三区在线观看| 国产一区二区亚洲| 色999日韩自偷自拍美女| 精品日韩在线| 一区精品在线| 一区二区三区毛片免费| 成人黄色片免费| 国产一区美女| 久草热视频在线观看| 午夜亚洲福利在线老司机| 日韩欧美精品在线观看视频| 视频一区免费在线观看| 手机看片福利盒子久久| 乱一区二区av| 青娱乐国产精品视频| 大尺度一区二区| 又黄又爽的网站| 久久久久久久电影| 网站永久看片免费| 亚洲综合色婷婷| 国产成人亚洲欧洲在线| 色999日韩国产欧美一区二区| 综合久久中文字幕| 欧美电影免费观看完整版| 性感美女福利视频| 国产亚洲视频在线观看| 伊人春色在线观看| 91精品国产自产91精品| 99久久精品一区二区成人| 91亚洲国产成人精品性色| 精品国产午夜肉伦伦影院| 欧美日韩喷水| 中文字幕一区二区三区久久网站| 国产成a人亚洲精v品在线观看| 国产精品亚洲欧美| 玖玖爱视频在线| 波多野结衣中文字幕一区二区三区| 四虎影成人精品a片| 亚洲欧洲精品天堂一级| 国产精品6666| 欧美日韩亚洲综合在线| 好吊色在线观看| 中文字幕亚洲图片| 好看的中文字幕在线播放| 国产精品大陆在线观看| 免费观看在线一区二区三区| 欧美日本韩国国产| 午夜精品剧场| 无码日韩人妻精品久久蜜桃| 国产盗摄一区二区三区| 99精品全国免费观看| 夜夜揉揉日日人人青青一国产精品| 五月婷婷激情视频| 日韩一区二区免费视频| 国产网站在线播放| 欧美黄色www| 成人污版视频| 欧美一区二区三区四区五区六区| 欧美理论在线| 亚洲久久中文字幕| 久久久午夜电影| 精品久久免费视频| 欧美一级搡bbbb搡bbbb| av免费观看一区二区| 91福利视频网| www.国产精品一区| 丰满女人性猛交| 日韩激情一二三区| 国产老熟女伦老熟妇露脸| 亚洲精品写真福利| 亚洲网站在线免费观看| 亚洲男人av电影| 69av成人| 国产精品亚洲不卡a| 综合久久久久| 九九热精品国产| 国产精品天美传媒沈樵| 人妻 日韩精品 中文字幕| 亚洲第一区第二区| 性欧美video高清bbw| 成人国产亚洲精品a区天堂华泰| 国产91精品对白在线播放| 无码粉嫩虎白一线天在线观看| 国产一区二区毛片| 成人自拍小视频| 欧美三级电影一区| 91在线看片| 国产精品美女免费| 欧美1级片网站| 污视频网站观看| 国产精品色哟哟| 夜夜躁狠狠躁日日躁av| 正在播放亚洲1区| 国产黄色精品| 在线不卡日本| 精久久久久久久久久久| 成人一级黄色大片| 欧美一区二区三区日韩视频| 精产国品自在线www| 91亚洲精华国产精华| 天天做天天爱天天综合网| 极品粉嫩美女露脸啪啪| 亚洲欧洲日产国码二区| 精品人妻一区二区三区麻豆91 | aaa免费在线观看| 国产一区二区视频在线| 91精品国产高清一区二区三蜜臀| 日韩一二三区视频| 91黄页在线观看| 精品视频一区在线| 久久狠狠婷婷| 日本午夜精品视频| 欧美丰满少妇xxxxx高潮对白| 国产激情小视频在线| 97免费资源站| 99热这里只有精品8| 日韩av一二区| 日本韩国一区二区| 女女色综合影院| 97se国产在线视频| 国产精品视区| 亚洲综合图片一区| 日韩精品影音先锋| 亚洲福利影院| 亚洲一区三区| 成人永久免费视频| 久久久久在线视频| 久久精品中文字幕免费mv| 粉嫩av一区二区| 国产熟人av一二三区| 亚洲女人的天堂| 四虎在线视频免费观看| 国产精品老女人精品视频| 欧美一区二区三区久久精品茉莉花| 精人妻一区二区三区| 91成人在线观看喷潮| 成人短视频在线| 蜜桃成人免费视频| 极品销魂美女一区二区三区| 精品人妻在线播放| 一区二区福利视频| 永久免费精品视频| 在线免费视频a| 亚洲综合自拍偷拍| 第一页在线观看| 国产伦精品一区二区三区视频黑人| 视频一区视频二区中文字幕| 久久久久久久久久久久久久免费看 | а天堂8中文最新版在线官网| 亚洲free性xxxx护士白浆| 久久久久久穴| 久久免费视频播放| 日韩综合中文字幕| 日韩欧美在线精品|