成人怡红院-成人怡红院视频在线观看-成人影视大全-成人影院203nnxyz-美女毛片在线看-美女免费黄

站長資訊網
最全最豐富的資訊網站

深入解析Vue3中的 diff 算法(圖文詳解)

本篇文章帶大家通過圖文來深入解析下Vue3中的 diff 算法,希望對大家有所幫助!

深入解析Vue3中的 diff 算法(圖文詳解)

本篇文章主要分析Vue3 diff算法,通過本文你可以知道:

  • diff的主要過程,核心邏輯

  • diff是如何進行節點復用、移動、卸載

  • 并有一個示例題,可以結合本文進行練習分析

如果你還不是特別了解Vnode、渲染器的patch流程,建議先閱讀下面兩篇文章:

  • Vnode(https://mp.weixin.qq.com/s/DtFJpA91UPJIevlqaPzcnQ)

  • 渲染器分析(https://mp.weixin.qq.com/s/hzpNGWFCLMC2vJNSmP2vsQ)

1.0 diffkey子節點

在處理被標記為UNKEYED_FRAGMENT時。

  • 首先會通過新舊自序列獲取最小共同長度commonLength

  • 對公共部分循環遍歷patch

  • patch 結束,再處理剩余的新舊節點。

  • 如果oldLength > newLength,說明需要對舊節點進行unmount

  • 否則,說明有新增節點,需要進行mount;

深入解析Vue3中的 diff 算法(圖文詳解)

這里貼下省略后的代碼。

const patchUnkeyedChildren = (c1, c2,...res) => {     c1 = c1 || EMPTY_ARR     c2 = c2 || EMPTY_ARR     // 獲取新舊子節點的長度     const oldLength = c1.length     const newLength = c2.length     // 1. 取得公共長度。最小長度     const commonLength = Math.min(oldLength, newLength)     let i     // 2. patch公共部分     for (i = 0; i < commonLength; i++) {        patch(...)     }     // 3. 卸載舊節點     if (oldLength > newLength) {       // remove old       unmountChildren(...)     } else {       // mount new       // 4. 否則掛載新的子節點       mountChildren(...)     }   }

從上面的代碼可以看出,在處理無key子節點的時候,邏輯還是非常簡單粗暴的。準確的說處理無key子節點的效率并不高。

因為不管是直接對公共部分patch,還是直接對新增節點進行mountChildren(其實是遍歷子節點,進行patch操作),其實都是在遞歸進行patch,這就會影響到性能。

2.0 diffkey子節點序列

diffkey子序列的時候,會進行細分處理。主要會經過以下一種情況的判斷:

  • 起始位置節點類型相同。
  • 結束位置節點類型相同。
  • 相同部分處理完,有新增節點。
  • 相同部分處理完,有舊節點需要卸載。
  • 首尾相同,但中間部分存在可復用亂序節點。

在開始階段,會先生面三個指正,分別是:

  • i = 0,指向新舊序列的開始位置
  • e1 = oldLength - 1,指向舊序列的結束位置
  • e2 = newLength - 1,指向新序列的結束位置

深入解析Vue3中的 diff 算法(圖文詳解)

let i = 0 const l2 = c2.length let e1 = c1.length - 1 // prev ending index let e2 = l2 - 1 // next ending index

下面開始分情況進行diff處理。

2.1 起始位置節點類型相同

深入解析Vue3中的 diff 算法(圖文詳解)

  • 對于起始位置類型相同的節點,從左向右進行diff遍歷。

  • 如果新舊節點類型相同,則進行patch處理

  • 節點類型不同,則break,跳出遍歷diff

//  i <= 2 && i <= 3 while (i <= e1 && i <= e2) {   const n1 = c1[i]   const n2 = c2[i]   if (isSameVNodeType(n1, n2)) {     // 如果是相同的節點類型,則進行遞歸patch     patch(...)   } else {     // 否則退出     break   }   i++ }

上面上略了部分代碼,但不影響主要邏輯。

從代碼可以知道,遍歷時,利用前面在函數全局上下文中聲明的三個指針,進行遍歷判斷。

保證能充分遍歷到開始位置相同的位置,i <= e1 && i <= e2

一旦遇到類型不同的節點,就會跳出diff遍歷。

2.2 結束位置節點類型相同

深入解析Vue3中的 diff 算法(圖文詳解)

開始位置相同diff 結束,會緊接著從序列尾部開始遍歷 diff

此時需要對尾指針e1、e2進行遞減。

//  i <= 2 && i <= 3 // 結束后: e1 = 0 e2 =  1 while (i <= e1 && i <= e2) {   const n1 = c1[e1]   const n2 = c2[e2]   if (isSameVNodeType(n1, n2)) {     // 相同的節點類型     patch(...)   } else {     // 否則退出     break   }   e1--   e2-- }

從代碼可以看出,diff邏輯與第一種基本一樣,相同類型進行patch處理。

不同類型break,跳出循環遍歷。

并且對尾指針進行遞減操作。

2.3 相同部分遍歷結束,新序列中有新增節點,進行掛載

經過上面兩種情況的處理,已經patch完首尾相同部分的節點,接下來是對新序列中的新增節點進行patch處理。

深入解析Vue3中的 diff 算法(圖文詳解)

在經過上面兩種請款處理之后,如果有新增節點,可能會出現 i > e1 && i <= e2的情況。

這種情況下意味著新的子節點序列中有新增節點。

這時會對新增節點進行patch

// 3. common sequence + mount // (a b) // (a b) c // i = 2, e1 = 1, e2 = 2 // (a b) // c (a b) // i = 0, e1 = -1, e2 = 0 if (i > e1) {   if (i <= e2) {     const nextPos = e2 + 1     // nextPos < l2,說明有已經patch過尾部節點,     // 否則會獲取父節點作為錨點     const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor     while (i <= e2) {       patch(null, c2[i], anchor, ...others)       i++     }   } }

從上面的代碼可以知道,patch的時候沒有傳第一個參數,最終會走mount的邏輯。

可以看這篇 主要分析patch的過程

https://mp.weixin.qq.com/s/hzpNGWFCLMC2vJNSmP2vsQ

patch的過程中,會遞增i指針。

并通過nextPos來獲取錨點。

如果nextPos < l2,則以已經patch的節點作為錨點,否則以父節點作為錨點。

2.4 相同部分遍歷結束,新序列中少節點,進行卸載

深入解析Vue3中的 diff 算法(圖文詳解)

如果處理完收尾相同的節點,出現i > e2 && i <= e1的情況,則意味著有舊節點需要進行卸載操作。

// 4. common sequence + unmount // (a b) c // (a b) // i = 2, e1 = 2, e2 = 1 // a (b c) // (b c) // i = 0, e1 = 0, e2 = -1 // 公共序列 卸載舊的 else if (i > e2) {   while (i <= e1) {     unmount(c1[i], parentComponent, parentSuspense, true)     i++   } }

通過代碼可以知道,這種情況下,會遞增i指針,對舊節點進行卸載。

2.5 亂序情況

這種情況下較為復雜,但diff的核心邏輯在于通過新舊節點的位置變化構建一個最大遞增子序列,最大子序列能保證通過最小的移動或者patch實現節點的復用。

下面一起來看下如何實現的。

深入解析Vue3中的 diff 算法(圖文詳解)

2.5.1 為新子節點構建key:index映射

深入解析Vue3中的 diff 算法(圖文詳解)

// 5. 亂序的情況 // [i ... e1 + 1]: a b [c d e] f g // [i ... e2 + 1]: a b [e d c h] f g // i = 2, e1 = 4, e2 = 5  const s1 = i // s1 = 2 const s2 = i // s2 = 2  // 5.1 build key:index map for newChildren // 首先為新的子節點構建在新的子序列中 key:index 的映射 // 通過map 創建的新的子節點 const keyToNewIndexMap = new Map() // 遍歷新的節點,為新節點設置key // i = 2; i <= 5 for (i = s2; i <= e2; i++) {   // 獲取的是新序列中的子節點   const nextChild = c2[i];   if (nextChild.key != null) {     // nextChild.key 已存在     // a b [e d c h] f g     // e:2 d:3 c:4 h:5     keyToNewIndexMap.set(nextChild.key, i)   } }

結合上面的圖和代碼可以知道:

  • 在經過首尾相同的patch處理之后,i = 2,e1 = 4,e2 = 5

  • 經過遍歷之后keyToNewIndexMap中,新節點的key:index的關系為 E : 2、D : 3 、C : 4、H : 5

  • keyToNewIndexMap的作用主要是后面通過遍歷舊子序列,確定可復用節點在新的子序列中的位置

2.5.2 從左向右遍歷舊子序列,patch匹配的相同類型的節點,移除不存在的節點

經過前面的處理,已經創建了keyToNewIndexMap

在開始從左向右遍歷之前,需要知道幾個變量的含義:

// 5.2 loop through old children left to be patched and try to patch // matching nodes & remove nodes that are no longer present // 從舊的子節點的左側開始循環遍歷進行patch。 // 并且patch匹配的節點 并移除不存在的節點  // 已經patch的節點個數 let patched = 0 // 需要patch的節點數量 // 以上圖為例:e2 = 5; s2 = 2; 知道需要patch的節點個數 // toBePatched = 4 const toBePatched = e2 - s2 + 1 // 用于判斷節點是否需要移動 // 當新舊隊列中出現可復用節點交叉時,moved = true let moved = false // used to track whether any node has moved // 用于記錄節點是否已經移動 let maxNewIndexSoFar = 0  // works as Map<newIndex, oldIndex> // 作新舊節點的下標映射 // Note that oldIndex is offset by +1 // 注意 舊節點的 index 要向右偏移一個下標  // and oldIndex = 0 is a special value indicating the new node has // no corresponding old node. // 并且舊節點Index = 0 是一個特殊的值,用于表示新的節點中沒有對應的舊節點  // used for determining longest stable subsequence // newIndexToOldIndexMap 用于確定最長遞增子序列 // 新下標與舊下標的map const newIndexToOldIndexMap = new Array(toBePatched) // 將所有的值初始化為0 // [0, 0, 0, 0] for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
  • 變量 patched 用于記錄已經patch的節點
  • 變量 toBePatched 用于記錄需要進行patch的節點個數
  • 變量 moved 用于記錄是否有可復用節點發生交叉
  • maxNewIndexSoFar用于記錄當舊的子序列中存在沒有設置key的子節點,但是該子節點出現于新的子序列中,且可復用,最大下標。
  • 變量newIndexToOldIndexMap用于映射新的子序列中的節點下標 對應于 舊的子序列中的節點的下標
  • 并且會將newIndexToOldIndexMap初始化為一個全0數組,[0, 0, 0, 0]

深入解析Vue3中的 diff 算法(圖文詳解)

知道了這些變量的含義之后 我們就可以開始從左向右遍歷子序列了。

遍歷的時候,需要首先遍歷舊子序列,起點是s1,終點是e1

遍歷的過程中會對patched進行累加。

卸載舊節點

如果patched >= toBePatched,說明新子序列中的子節點少于舊子序列中的節點數量。

需要對舊子節點進行卸載。

// 遍歷未處理舊序列中子節點 for (i = s1; i <= e1; i++) {     // 獲取舊節點     // 會逐個獲取 c d e     const prevChild = c1[i]     // 如果已經patch 的數量 >= 需要進行patch的節點個數          // patched剛開始為 0     // patched >= 4     if (patched >= toBePatched) {       // all new children have been patched so this can only be a removal       // 這說明所有的新節點已經被patch 因此可以移除舊的       unmount(prevChild, parentComponent, parentSuspense, true)       continue     } }

如果prevChild.key是存在的,會通過前面我們構建的keyToNewIndexMap,獲取prevChild在新子序列中的下標newIndex

獲取newIndex

// 新節點下標 let newIndex if (prevChild.key != null) {   // 舊的節點肯定有key,    // 根據舊節點key  獲取相同類型的新的子節點  在 新的隊列中對應節點位置   // 這個時候 因為c d e 是原來的節點 并且有key   // h 是新增節點 舊節點中沒有 獲取不到 對應的index 會走else   // 所以newIndex在開始時會有如下情況   /**    * node  newIndex    *  c       4    *  d       3    *  e       2    * */    // 這里是可以獲取到newIndex的   newIndex = keyToNewIndexMap.get(prevChild.key) }

以圖為例,可以知道,在遍歷過程中,節點c、d、e為可復用節點,分別對應新子序列中的2、3、4的位置。

newIndex可以取到的值為4、3、2

如果舊節點沒有key怎么辦?

// key-less node, try to locate a key-less node of the same type // 如果舊的節點沒有key // 則會查找沒有key的 且為相同類型的新節點在 新節點隊列中 的位置 // j = 2: j <= 5 for (j = s2; j <= e2; j++) {   if (     newIndexToOldIndexMap[j - s2] === 0 &&     // 判斷是否是新舊節點是否相同     isSameVNodeType(prevChild, c2[j])   ) {     // 獲取到相同類型節點的下標     newIndex = j     break   } }

如果節點沒有key,則同樣會取新子序列中,遍歷查找沒有key且兩個新舊類型相同子節點,并以此節點的下標,作為newIndex

newIndexToOldIndexMap[j – s2] === 0 說明節點沒有該節點沒有key。

如果還沒有獲取到newIndex,說明在新子序列中沒有存在的與 prevChild 相同的子節點,需要對prevChild進行卸載。

if (newIndex === undefined) {   // 沒有對應的新節點 卸載舊的   unmount(prevChild, parentComponent, parentSuspense, true) }

否則,開始根據newIndex,構建keyToNewIndexMap,明確新舊節點對應的下標位置。

時刻牢記newIndex是根據舊節點獲取的其在新的子序列中的下標。

// 這里處理獲取到newIndex的情況 // 開始整理新節點下標 Index 對于 相同類型舊節點在 舊隊列中的映射 // 新節點下標從 s2=2 開始,對應的舊節點下標需要偏移一個下標 // 0 表示當前節點沒有對應的舊節點 // 偏移 1個位置 i從 s1 = 2 開始,s2 = 2 // 4 - 2 獲取下標 2,新的 c 節點對應舊 c 節點的位置下標 3 // 3 - 2 獲取下標 1,新的 d 節點對應舊 d 節點的位置下標 4 // 2 - 2 獲取下標 0,新的 e 節點對應舊 e 節點的位置下標 5 // [0, 0, 0, 0] => [5, 4, 3, 0] // [2,3,4,5] = [5, 4, 3, 0] newIndexToOldIndexMap[newIndex - s2] = i + 1 // newIndex 會取 4 3 2 /**   *   newIndex  maxNewIndexSoFar   moved  *       4            0          false  *       3            4           true  *       2          *   * */  if (newIndex >= maxNewIndexSoFar) {   maxNewIndexSoFar = newIndex } else {   moved = true }

在構建newIndexToOldIndexMap的同時,會通過判斷newIndexmaxNewIndexSoFa的關系,確定節點是否發生移動。

newIndexToOldIndexMap最后遍歷結束應該為[5, 4, 3, 0]0說明有舊序列中沒有與心序列中對應的節點,并且該節點可能是新增節點。

如果新舊節點在序列中相對位置保持始終不變,則maxNewIndexSoFar會隨著newIndex的遞增而遞增。

意味著節點沒有發生交叉。也就不需要移動可復用節點。

否則可復用節點發生了移動,需要對可復用節點進行move

遍歷的最后,會對新舊節點進行patch,并對patched進行累加,記錄已經處理過幾個節點。

// 進行遞歸patch /**  * old   new  *  c     c  *  d     d  *  e     e  */ patch(   prevChild,   c2[newIndex],   container,   null,   parentComponent,   parentSuspense,   isSVG,   slotScopeIds,   optimized ) // 已經patch的 patched++

經過上面的處理,已經完成對舊節點進行了卸載,對相對位置保持沒有變化的子節點進行了patch復用。

接下來就是需要移動可復用節點,掛載新子序列中新增節點。

2.5.3 移動可復用節點,掛載新增節點

這里涉及到一塊比較核心的代碼,也是Vue3 diff效率提升的關鍵所在。

前面通過newIndexToOldIndexMap,記錄了新舊子節點變化前后的下標映射。

這里會通過getSequence方法獲取一個最大遞增子序列。用于記錄相對位置沒有發生變化的子節點的下標。

根據此遞增子序列,可以實現在移動可復用節點的時候,只移動相對位置前后發生變化的子節點。

做到最小改動。

那什么是最大遞增子序列?

  • 子序列是由數組派生而來的序列,刪除(或不刪除)數組中的元素而不改變其余元素的順序。
  • 而遞增子序列,是數組派生的子序列,各元素之間保持逐個遞增的關系。
  • 例如:
  • 數組[3, 6, 2, 7] 是數組 [0, 3, 1, 6, 2, 2, 7] 的最長嚴格遞增子序列。
  • 數組[2, 3, 7, 101] 是數組 [10 , 9, 2, 5, 3, 7, 101, 18]的最大遞增子序列。
  • 數組[0, 1, 2, 3] 是數組 [0, 1, 0, 3, 2, 3]的最大遞增子序列。

深入解析Vue3中的 diff 算法(圖文詳解)

已上圖為例,在未處理的亂序節點中,存在新增節點N、I、需要卸載的節點G,及可復用節點C、D、E、F

節點CDE在新舊子序列中相對位置沒有變換,如果想要通過最小變動實現節點復用,我們可以將找出F節點變化前后的下標位置,在新的子序列C節點之前插入F節點即可。

最大遞增子序列的作用就是通過新舊節點變化前后的映射,創建一個遞增數組,這樣就可以知道哪些節點在變化前后相對位置沒有發生變化,哪些節點需要進行移動。

Vue3中的遞增子序列的不同在于,它保存的是可復用節點在 newIndexToOldIndexMap的下標。而并不是newIndexToOldIndexMap中的元素。

接下來我們看下代碼部分:

// 5.3 move and mount // generate longest stable subsequence only when nodes have moved // 移動節點 掛載節點 // 僅當節點被移動后 生成最長遞增子序列 // 經過上面操作后,newIndexToOldIndexMap = [5, 4, 3, 0] // 得到 increasingNewIndexSequence = [2] const increasingNewIndexSequence = moved   ? getSequence(newIndexToOldIndexMap)   : EMPTY_ARR // j = 0 j = increasingNewIndexSequence.length - 1 // looping backwards so that we can use last patched node as anchor // 從后向前遍歷 以便于可以用最新的被patch的節點作為錨點 // i = 3 for (i = toBePatched - 1; i >= 0; i--) {   // 5 4 3 2   const nextIndex = s2 + i   // 節點 h  c  d  e    const nextChild = c2[nextIndex]   // 獲取錨點   const anchor =     nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor   // [5, 4, 3, 0] 節點h會被patch,其實是mount   //  c  d  e 會被移動   if (newIndexToOldIndexMap[i] === 0) {     // mount new     // 掛載新的     patch(       null,       nextChild,       container,       anchor,       ...     )   } else if (moved) {     // move if:     // There is no stable subsequence (e.g. a reverse)     // OR current node is not among the stable sequence     // 如果沒有最長遞增子序列或者 當前節點不在遞增子序列中間     // 則移動節點     //      if (j < 0 || i !== increasingNewIndexSequence[j]) {       move(nextChild, container, anchor, MoveType.REORDER)     } else {       j--     }   } }

深入解析Vue3中的 diff 算法(圖文詳解)

從上面的代碼可以知道:

  • 通過newIndexToOldIndexMap獲取的最大遞增子序列是[2]
  • j = 0
  • 遍歷的時候從右向左遍歷,這樣可以獲取到錨點,如果有已經經過patch的兄弟節點,則以兄弟節點作為錨點,否則以父節點作為錨點
  • newIndexToOldIndexMap[i] === 0,說明是新增節點。需要對節點進行mount,這時只需給patch的第一個參數傳null即可。可以知道首先會對h節點進行patch
  • 否則會判斷moved是否為true。通過前面的分析,我們知道節點C & 節點E在前后變化中發生了位置移動。
  • 故這里會移動節點,我們知道 j 此時為0i 此時為**2**,i !== increasingNewIndexSequence[j]true,并不會移動C節點,而是執行 j--
  • 后面因為 j < 0,會對 D、E節點進行移動。

至此我們就完成了Vue3 diff算法的學習分析。

這里為大家提供了一個示例,可以結合本文的分析過程進行練習:

可以只看第一張圖進行分析,分析結束后可以與第二三張圖片進行對比。

圖一:

深入解析Vue3中的 diff 算法(圖文詳解)

圖二 & 三:

深入解析Vue3中的 diff 算法(圖文詳解)

深入解析Vue3中的 diff 算法(圖文詳解)

總結

通過上面的學習分析,可以知道,Vue3diff算法,會首先進行收尾相同節點的patch處理,結束后,會掛載新增節點,卸載舊節點。

如果子序列的情況較為復雜,比如出現亂序的情況,則會首先找出可復用的節點,并通過可復用節點的位置映射構建一個最大遞增子序列,通過最大遞增子序列來對節點進行mount & move。以提高diff效率,實現節點復用的最大可能性。

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
国产福利在线永久视频| 大肥女BBWBBWHD视频| JAPANESE国产乱在线播放| 被陌生人在地铁揉到高潮| 动漫精品中文无码卡通动漫| 国产成人片无码视频在线观看| 国产熟女乱子视频正在播放| 久久国内精品自在自线400部| 美女MM131爽爽爽作爱视频| 欧美老熟妇乱人伦人妻| 色婷婷综合久久久久中文字幕| 无码VR最新无码AV专区| 亚洲国产AⅤ成人精品无吗| 永久免费AⅤ无码网站国产| Chinese老熟女老女人HD| 隔着超薄肉丝袜做AV在线| 国产在线不卡人成视频| 久青草国产97香蕉在线影院| 欧洲熟妇的性久久久久久| 视频无码一区二区| 亚洲欧美国产免费综合视频| 97久久精品无码一区二区| 高清成人爽A毛片免费| 精品人妻AV区乱码| 欧美free叉叉叉叉极品少妇| 少妇人妻精品一区二区| 亚洲国产群交无码AV| 337P日本大胆欧洲色噜噜| 夫妻二人体验交换夫妻的后果| 好男人影视官网在线WWW| 免费又黄又爽1000禁片| 四虎精品成人免费视频| 亚洲人妻免费视频| TPU色母和子色母的性能| 国产免费人成视频在线播放播| 久久久久亚洲AV成人无码网站| 欧美人与物VIDEOS另类| 午夜精品一区二区三区在线视| 亚洲中文字幕无码永久在线| 边做饭边被躁BD在线播放| 含羞草自慰抽搐喷白浆AⅤ| 欧美疯狂性受XXXXX另类| 无码国产偷倩在线播放| 正在播放国产对白孕妇作爱| 公侵犯玩弄漂亮人妻优| 久久久国产精品亚洲一区| 日韩一区二区三区视频| 亚洲午夜精品久久久久久APP| 草莓丝瓜向日葵黄瓜榴莲IOS| 黑人巨鞭大战丰满少妇| 青青草原综合久久大伊人| 亚洲AV中文无码字幕色本草| WYC忘忧草在线看WWW| 韩国三级HD中文字幕叫床| 欧美专区日韩视频人妻| 亚洲国产精品久久久久蜜桃网站| XXXAV久久久久久久久久久| 激情欧美日韩一区二区| 日本熟妇厨房XXXXX乱电影| 亚洲综合色婷婷七月丁香| 非洲黑人狂躁日本妞| 老汉吸奶水捏奶头小说| 无码人妻丝袜在线视频| 99久在线国内在线播放免费观看| 国内精品人妻久久毛片APP| 强 暴 疼 哭 处 女| 亚洲人成色777777在线观看| 高清WINDOWS免费版| 免费无码午夜福利电影网| 亚洲AV福利天堂一区二区三| 被主人调教边C边打屁股作文| 久久精品国产一区二区三区肥胖| 特级西西WWW444人体聚色| 97人妻无码一区二区精品免费| 黑人刚破完处就三P| 色综合色狠狠天天综合网| 中日韩精品无码一区二区三区| 国产曰的好深好爽免费视频 | 精品国产一区二区三区噜噜噜| 日产乱码一二三区别免费影视 | 久久婷婷五月综合97色直播| 无码午夜福利视频一区| 凹凸人妻人人澡人人添| 狼人无码精华AV午夜精品| 亚洲AV日韩专区在线观看| 大又大粗又爽又黄少妇毛片免费 | 精品乱人伦一区二区三区| 精品精品国产高清A毛片| 亚洲AV综合色一区二区三区| 成人精品一区二区三区在线观看 | 精产国品一二三产区区别在哪儿 | 国产又爽又黄又舒服又刺激视频 | 亚洲AV成人综合五月天在线观看| 亚洲AV片不卡无码一| 亚洲性爱一区二区| 27邪态恶动图GIF喷水赞一把| 国产真实乱XXXⅩ视频| 男女爽爽午夜18禁影院免费| 色欲AV无码中字乱人伦在线| 无码中文人妻在线三区| 亚洲欧美日韩国产精品一区二区| 99精品国产福久久久久久| 处 女 开 破视频处CT开| 久久99久久99精品免观看吃奶| 欧美人与禽XOXO性伦交| 亚洲国产成人精品青青草原导航| 亚洲欧美国产成人综合不卡| JIZZ成熟丰满韩国女人少妇| 国产精品乱子伦XXXX| 清一区二区国产好的精华液| 亚洲国产精品久久久久蜜桃噜噜| 大陆国语对白国产AV片| 和朋友换娶妻野外夫妇3| 女人被爽到呻吟GIF动态图| 无码成人亚洲AV污污污在线看| 亚洲精品第一国产综合麻豆| 成 人 网 站国产免费观看| 国产午夜成人无码免费看不卡| 色哟哟最新在线观看入口| 亚洲成AV人片不卡无苍井空| BGMBGMBGM胖老太太XX| 狠狠干2015最新版| 亚洲AV无码久久寂寞少妇| 99久久无码一区人妻| 精品人妻无码一区二| 色一情一乱一伦一区二区三区日本| 92午夜少妇极品福利无码电影| 美女把尿口扒开让男人桶| 中文字幕亚洲欧美专区| 国产精品人成在线播放新网站| 少妇人妻14页_麻花色| 公与憩止痒小说400章| 熟妇人妻不卡无码一区| 国产成人精品无码一区二区老年人 | 舌头伸进去添的我好爽高视频 | 重生之玩遍娱乐圈全文阅读| 妺妺窝人体色www在线观看婚闹| 18无码粉嫩小泬无套在线观看| 妺妺窝人体色WWW人体色| 99久久免费精品高清特色大片 | 无码人妻精品一区二区三区蜜桃| 国产精品YY9299在线观看| 无码专区 人妻系列 在线| 国产无夜激无码AV毛片| 亚洲AV中文无码乱人伦在线R| 精品人妻AV一区二区三区| 野花日本大全免费观看2019| 蜜桃视频在线观看| AV无码久久久久不卡网站蜜桃| 人妻少妇中文字幕| 肥熟老熟妇500部视频| 洗澡被公强玩好舒服肉欲小说| 含紧一点H边做边走动| 亚洲最刺激成人无码| 免费视频片多多视频免费高清| GV天堂GV无码男同在线观看| 日韩乱码人妻无码超清蜜桃| 国产放荡AV剧情演绎麻豆| 亚洲AV无码成人| 久久久久久午夜成人影院| 99久久亚洲综合精品成人| 日韩毛片无码永久免费看| 国产精品电影久久久久电影网| 亚洲AV永久无码精品无码四虎| 老妇乱强伦XXXXX| JEΑL0USVUE成熟50M| 熟妇人妻AV中文字幕老熟妇| 国产亚州精品女人久久久久久| 亚洲一区在线观看XXX| 米奇影院888奇米色| 成人福利国产午夜AV免费不卡在| 无码精品视频一区二区三区| 精产国品一二三产品区别在哪| 中文字幕AV无码一区电影DVD | 久久人妻无码中文字幕| 锕锕锕锕锕锕~好深啊APP下载| 天堂√最新版中文在线地址| 狠狠色综合久久久久尤物| 中文字幕乱近親相姦| 日韩日韩日韩日韩日韩| 含着她的花蒂啃咬高潮| 中文字幕一区二区三区乱码视频 | 美女扒开粉嫩尿口的照片| 东京热一区二区三区无码视频| 亚洲AV成人永久网站www在线| 里番本子侵犯肉全彩无码| 成人做受120视频试看| 亚洲AV无码一区二区在线蜜桃| 美女与野兽在线观看| 高H日本视频一区| 亚洲熟妇无码AV在线少妇| 人禽杂交18禁网站免费| 国产毛多水多五月激情四射| 在公交车上弄到高C了| 私人电影院免费看吗| 久久国产精品久久久久久| 白嫩光屁股BBBBBBBBB| 亚洲国产成人无码网站大全| 欧亚成年男女深夜百度网盘| 国产亚洲美女精品久久久2020|