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

Continuation在JS中的應用

開發 前端
本文著眼于支撐這些功能的一個底層編程概念 Continuation(譯作“續延”),期望能夠在了解它之后,大家對這幾個功能有進一步的理解和掌握。

 正文從這開始~~

React 新近發布的 Hooks、Suspense、Concurrent Mode 等新功能讓人眼前一亮,甚至驚嘆 JS 居然有如此魔力。同時,這幾個功能或多或少附帶一些略顯奇怪的規則,沒有更深層次理解的話難以把握。其實這里面并沒有什么“黑科技”,就大的趨勢來講,前端整體上還是在不斷借鑒計算機其它領域的優秀實踐,來幫助我們更方便地解決人機交互問題。本文著眼于支撐這些功能的一個底層編程概念 Continuation(譯作“續延”),期望能夠在了解它之后,大家對這幾個功能有進一步的理解和掌握。當然,Continuation 在 React 之外也有很多的應用,可以一眼窺豹。

Continuation 是什么?

有些人對 continuation 并不陌生,因為有時候在談到 Callback Hell(回調地獄)時會有提到這一概念。但其實它和回調函數大相徑庭。

維基百科對它的定義是:

A continuation is an abstract representation of the control state of a computer program.

即,continuation 是計算機程序控制狀態的抽象表示。一個坊間更通俗的說法是:它代表程序的剩余部分。像 continue、break 這類控制流操作符一樣,continuation 能夠暴露給用戶程序從而可以在恰當時機恢復執行,這種基本能力大大擴展了編程語言使用者的發揮空間,也為 excpetion handling、generators、coroutines、algebraic effects 等提供了堅實基礎。

相信很多人和我一樣,對這樣不明就里的官方解釋迷惑不解。沒關系,我們首先舉一個現實生活中的例子——Continuation 三明治:

默認 Continuation

事實上,所有的程序都自帶一個默認的 continuation,那就是調用棧(Call Stack)。調用棧中存放著當前程序的一系列剩余任務,每個任務在調用棧中表示為一個棧幀(Stack Frame),用以存放任務的數據、變量和調用信息。當調用棧為空時,意味著整個程序執行結束了。

   

  1. function main() {  
  2.       foo();  
  3.       bar();  
  4.     }  
  5.     function foo() {  
  6.       bar();  
  7.     }  
  8.     function bar() {  
  9.       // do something  
  10.     } 

可以看出,調用棧是嚴格按照后進先出的方式運行的,無法靈活調整執行順序。此外,控制流的控制權也被運行環境牢牢掌握,程序員無能為力。

現在,讓我們設想下,如果未來有一天我們能夠將調用任務以鏈表的方式存儲在堆中,是不是就可以突破調用棧的限制了呢?

[[327178]]

首先,因為任務以調用楨的形式存儲在堆中,并通過指針相互關聯,形成一個調用幀鏈表。當前任務完成時,運行時可以使用這些指針跳到下一個調用幀。得益于鏈表這一組織形式,執行程序有能力調整調用幀之間的結構順序。

Continuation-Passing Style (CPS)

為了獲得更多控制權,廣大程序員們進行了艱苦卓絕的努力。CPS 即是第一種有意義的嘗試。簡單來說,它是將程序控制流通過 continuation 的形式進行顯示傳遞的一種編程方式,具體有三個典型特征:

  •  每個函數的最后一個參數都是它的 continuation
  •  函數內部不能顯示地使用 return
  •  函數只能通過調用 continuation 以傳遞它完成的計算結果

舉個栗子:   

  1. function double(x, next) {  
  2.      next(x * 2);  
  3.    }  
  4.    function add(x, y, next) {  
  5.      next(x + y);  
  6.    }  
  7.    function minus(x, y, next) {  
  8.      next(x - y);  
  9.    }  
  10.    // ((1 + 2) - 5) * 2  
  11.    add(1, 2, resultAdd => {  
  12.      minus(resultAdd, 5, resultMinus => {  
  13.        double(resultMinus, resultDouble => {  
  14.          console.log(`result: ${resultDouble}`);  
  15.        });  
  16.      });  
  17.    }); 

這不就是我們前端工程師耳熟能詳的回調函數么,最后的調用也再次讓我們想起了恐怖的回調地獄。表面上看的確如此,但是從控制流的角度來進一步考慮,這種模式的確賦予了程序員更多控制權,因為所有的計算步驟(函數)的 continuation 都是顯示傳遞的。

例如,假設我們希望能夠在計算的中間點進行檢查,一旦計算結果小于 0 則直接返回該結果?;?CPS 的三點特征,我們可以定義如下一個 evaluate 的計算過程:   

  1. function evaluate(frames, operate, next) {  
  2.      let output;  
  3.      const run = (index) => {  
  4.        // Finish all frames, go to run the top level continuation  
  5.        if (index === frames.length) return next(output);  
  6.        // Pick up the next frame and run it with assembled arguments  
  7.        const { fn, args = [] } = frames[index];  
  8.        const fnArgs = index > 0 ? [output, ...args] : [...args];  
  9.        fnArgs.push((result) => {  
  10.          output = result 
  11.          operate(output, next, () => run(++index));  
  12.        });  
  13.        fn(...fnArgs);  
  14.      };  
  15.      // Kick off  
  16.      run(0);  
  17.    }  
  18.    // ((1 + 2) - 5) * 2  
  19.    evaluate(  
  20.      [  
  21.        { fn: add, args: [1, 2] },  
  22.        { fn: minus, args: [5] },  
  23.        { fn: double },  
  24.      ],  
  25.      (output, abort, next) => {  
  26.        if (output < 0) return abort(`the intermedia result is less than zero: ${output}`);  
  27.        next(output);  
  28.      },  
  29.      (output) => {  
  30.        console.log(`output: ${output}`);  
  31.      },  
  32.    ); 

示例:https://jsbin.com/bidayeg/3/edit?js,console

可以看出,一方面,通過合理組織計算步驟模型,evaluate 可以幫助避免回調地獄的問題,另一方面,evaluate 的第二個參數會在每個計算步驟完成時進行檢查,并且有能力 abort 后續所有計算步驟,直接調用頂層 continuation 返回中間結果。

這個示例展示了 CPS 為我們拓展的控制流操作能力,除此之外,CPS 還有如下優點:

  • 尾調用。每個函數都是在最后一個動作調用 continuation 返回計算結果,因此執行上下文不需要被保存到當前調用棧,編譯器可以針對這種情況做尾調用消除(Tail Call Elimination)的優化,這種優化在函數式語言編譯器中大量應用
  •  異步友好。眾所眾知,JavaScript 是單線程的,如果使用直接函數調用來處理遠程請求等操作,那么我們將不得不暫停這唯一的線程直到異步操作結果返回,這意味著用戶的其它交互得不到及時響應。CPS 或者換言之的回調模式提供了一種有效易用的方式來處理這類問題

然而,程序終究是人來編寫和維護的,CPS 雖然有眾多好處,但讓所有人都遵循這樣嚴格的方式編程非常困難,目前這種技術更多地在編譯器中作為中間表示層應用。

CallCC

目前 Continuation 的主流應用方式是通過形如 callCC(call with current continuation)的過程調用以捕獲當前 continuation,并在之后適時執行它以恢復到 continuation 所在上下文繼續執行后續計算從而實現各種控制流操作。

Scheme、Scala 等語言提供了 call/cc 或等效控制流操作符,JS 目前并沒有原生支持,但是通過后續介紹的兩種方式可以間接實現。

現在假設我們已經可以在 JS 中使用 callCC 操作符,讓我們試試看它都能為我們帶來什么樣的頭腦風暴吧。

小試牛刀

讓我們從一個非常簡單的例子開始,了解下 callCC 如何運作:   

  1. const x = callCC(function (cont) {  
  2.      for (let i = 0; i < 10; i++) {  
  3.        if (i === 3) {  
  4.          cont('done');  
  5.        }  
  6.        console.log(i);  
  7.      }  
  8.    });  
  9.    console.log(x);  
  10.    // output:  
  11.    // 0  
  12.    // 1  
  13.    // 2  
  14.    // done 

從輸出結果可以看出,程序的 for 循環并沒有全部完成,而是在 i 為 3 時執行 callCC 捕獲的 continuation 過程時直接退出了整個 callCC 調用,并將 'done' 返回給了變量 x。我們可以總結下 callCC 方法的邏輯:

  •  接受一個函數為唯一參數
  •  該函數也有唯一一個參數 cont,代表 callCC 的后續計算,在這個例子中,即將 callCC 的計算結果賦值給 x,然后執行最后的 console.log(x) 打印結果
  •  callCC 會立即調用其函數參數
  •  在該函數參數執行過程中,cont 可以接受一個參數作為 callCC 的返回值,一旦調用,則忽略后續所有計算,程序控制流跳轉會 callCC 的調用處繼續執行

得益于 James Long 開發的 Unwinder 在線編譯工具,非常推薦各位去 Simple 示例 嘗試在瀏覽器里執行下,你甚至可以打斷點然后單步執行哦~

重新實現列表 some 方法

進一步地,讓我們檢驗下剛剛介紹的對 callCC 的理解,重新實現下列表的 some 方法:   

  1. function some(predicate, arr) {  
  2.      const x = callCC(function (cont) {  
  3.        for (let index = 0; index < arr.length; index++) {  
  4.          console.log('testing', arr[index]);  
  5.          if (predicate(arr[index])) {  
  6.            cont(true);  
  7.          }  
  8.        }  
  9.        return false;  
  10.      });  
  11.      return x;  
  12.    }  
  13.    console.log(some(x => x >= 2, [1, 2, 3, 4])); 
  14.    console.log(some(x => x >= 2, [1, -5]));  
  15.    // output:  
  16.    // testing 1  
  17.    // testing 2  
  18.    // true  
  19.    // testing 1 
  20.    // testing -5  
  21.    // false 

在第一個 some 函數調用中,當 predicate 返回為 true 時,cont(true) 執行后程序控制流跳轉到 callCC 調用處,然后 some 函數返回 true 并被打印。然而在第二個 some 調用中,因為所有 predicate 都為 false,沒有 cont 被調用,因此 callCC 返回了其函數參數的最后一個 return 語句的結果。

在這個例子中,我們進一步了解了 callCC 的運行原理,并能用它實現一些工具方法。

重新實現 Try-Catch

接下來,讓我們挑戰一個難度更大的 callCC 應用:重寫 try-catch。 

  1. const tryStack = [];  
  2.   function Try(body, handler) {  
  3.     const ret = callCC(function (cont) {  
  4.       tryStack.push(cont);  
  5.       return body();  
  6.     });  
  7.     tryStack.pop();  
  8.     if (ret.__exc) {  
  9.       return handler(ret.__exc);  
  10.     }  
  11.     return ret;  
  12.   }  
  13.   function Throw(exc) {  
  14.     if (tryStack.length > 0) {  
  15.       tryStack[tryStack.length - 1]({ __exc: exc });  
  16.     }  
  17.     console.log("unhandled exception", exc);  
  18.   } 

Try 函數接受兩個參數:body 是接下來準備執行的主體邏輯,handler 是異常處理邏輯。關鍵點在于 Try 內部在執行 body 前會先將捕獲的 cont 壓入到堆棧 tryStack 中,以便在 Throw 時獲取 cont 從而繼續從 callCC 調用處恢復,從而實現類似 try-catch 語句的功能。

下面是一個 Try-Catch 的應用示例: 

  1. function bar(x) {  
  2.     if (x < 0) {  
  3.       Throw(new Error("error!"));  
  4.     }  
  5.     return x * 2;  
  6.   }  
  7.   function foo(x) {  
  8.     return bar(x);  
  9.   }  
  10.   Try(  
  11.     function () { 
  12.       console.log(foo(1));  
  13.       console.log(foo(-1));  
  14.       console.log(foo(2));  
  15.     },  
  16.     function (ex) { 
  17.       console.log("caught", ex);  
  18.     }  
  19.   );  
  20.   // output:  
  21.   // 2  
  22.   // caught Error: error! 

和我們預期的效果一致,異常處理函數可以捕獲 Throw 拋出的異常,同時主體邏輯 body 中的剩余部分也不再執行。另外,Throw 也像 JavaScript 原生的 throw 一樣,能夠擊穿多層函數調用,直到被 Try 語句的異常處理邏輯處理。

可恢復的 Try-Catch

基于上一小節中 Try-Catch 實現,我們現在嘗試一個真正的能體現 continuation 魔力的改造:讓 Try-Catch 在捕獲異常后,能夠從拋出異常的地方恢復執行。

為了實現這一效果,我們只需要對 Throw 進行改造,使其也通過 callCC 過程捕獲調用 Throw 時的 continuation,并將該 continuation 賦值給異常對象以供 Resume 過程調用從而實現異?;謴停?nbsp;

  1. function Throw(exc) {  
  2.   if (tryStack.length > 0) {  
  3.     return callCC(function (cont) {  
  4.       exc.__cont = cont;  
  5.       tryStack[tryStack.length - 1]({ __exc: exc });  
  6.     });  
  7.   } 
  8.   throw exc;  
  9.  
  10. function Resume(exc, value) {  
  11.   exc.__cont(value);  

實際使用的例子如下:   

  1. function double(x) {  
  2.       console.log('x is', x);  
  3.       if (x < 0) { 
  4.         x = Throw({ BAD_NUMBER: x });  
  5.       }  
  6.       return x * 2;  
  7.     }  
  8.     function main(x) { 
  9.       return double(x);  
  10.     }  
  11.     Try(  
  12.       function () {  
  13.         console.log(main(1));  
  14.         console.log(main(-2));  
  15.         console.log(main(3));  
  16.       },  
  17.       function (ex) {  
  18.         if (typeof ex.BAD_NUMBER !== 'undefined') {  
  19.           Resume(ex, Math.abs(ex.BAD_NUMBER));  
  20.         }  
  21.         console.log('caught', ex);  
  22.       }  
  23.     );  
  24.     // output:  
  25.     // x is 1  
  26.     // 2  
  27.     // x is -2  
  28.     // 4  
  29.     // x is 3  
  30.     // 6 

從上例輸出中,我們可以清晰地注意到,在執行 main(-2) 時拋出的錯誤被準確地識別并且恢復為正確的正整數,并最終執行完所有主體邏輯。

Algebraic Effects

這種異?;謴偷臋C制,也被稱作 Algebraic Effects。它有一個非常核心的優勢:將主體邏輯與異?;謴瓦壿嫹蛛x。例如我們可以在 UI 組件中拋出一個數據讀取的異常,然后在更上層的異常處理邏輯中嘗試獲取該數據后恢復執行,這樣既簡化了 UI 組件的復雜度,也將數據獲取的邏輯交給了調用方,更加靈活高效。

實際上 Algebraic Effects 還有著諸多的應用,Eff、Ocaml 等編程語言對 Algebraic Effects 有著豐富的支持。React 有不少團隊成員是 Ocaml 的擁躉,新近推出的 Hooks、Suspense 都深受這種思想啟發,能夠讓我們類似線性同步地調用各種狀態讀取、數據獲取等異步過程。

下面我們來分析一個 Suspense 示例,體會下背后解決思路的相似之處:   

  1. function ProfilePage() {  
  2.      return ( 
  3.        <Suspense fallback={<h1>Loading profile...</h1>}> 
  4.          <ProfileDetails /> 
  5.        </Suspense>  
  6.      );  
  7.    }  
  8.    function ProfileDetails() {  
  9.      // Try to read user info, although it might not have loaded yet  
  10.      const user = resource.user.read();  
  11.      return <h1>{user.name}</h1> 
  12.    }  
  13.    const rootElement = document.getElementById("root");  
  14.    ReactDOM.createRoot(rootElement).render(  
  15.      <ProfilePage />  
  16.    ); 

在 ProfileDetails 組件中,執行 resource.user.read() 時,由于當前數據并不存在,所以需要 throw 一個 promise 實例。位于上層的 Suspense 在捕獲這個 promise 后會先展示 fallback 指定的 UI,然后等待 promise resolve 后再次嘗試渲染 ProfileDetails 組件。雖然對比基于 Continuation 實現的異常恢復仍然有一定差距,并不能精確地從主體邏輯中拋出異常的語句處恢復,而是將主體邏輯重新執行一遍。不過 React 內部做了大量優化,盡最大可能地避免不必要開銷。

CallCC 實現

相信很多讀者在一覽 callCC 的強大能力之后,已經忍不住想要盡快了解下它的實現方式,很難想象土鱉的 JS 是如何能做到這一切的。這一章節我們就為大家揭開它的神秘面紗。

編譯

類似 Babel 幫助我們將各種 JS 新標準甚至是草案階段的語言特性轉化為主流瀏覽器都能運行的最終代碼一樣,我們可以借助增加一個編譯階段將含有 callCC 調用的代碼轉化為普通瀏覽器都能運行的代碼。

Prettier 作者 James Long 早些年開發網頁游戲編輯器時曾打算制作一款交互式代碼調試工具,種種嘗試之后,他在友人的指導下學習了 Exceptional Continuations in JavaScript 論文中介紹的高性能方法,并基于當時 Facebook 剛剛開源不久的編譯 generator 利器 Regenerator,開發了 Unwinder 來編譯 callCC,同時還提供了一個運行時以及實時在線 debug 工具。

Unwinder 或者說 Regenerator 的核心是狀態機,即將源代碼中的所有計算步驟打散,相互之間的跳轉通過狀態變換來進行。例如下面這段簡短的代碼:   

  1. function foo() {  
  2.      var x = 5 
  3.      var y = 6 
  4.      return x + y;  
  5.    } 

在經過狀態機轉換后,變成了如下形式:   

  1. function foo() {  
  2.       let $__next = 0, x, y;  
  3.       while (1) {  
  4.         switch($__next) {  
  5.           case 0:  
  6.             x = 5 
  7.             $__next = 1 
  8.             break;  
  9.           case 1:  
  10.             y = 6 
  11.             $__next = 2 
  12.             break;  
  13.           case 2:  
  14.             return x + y;  
  15.         }  
  16.       }  
  17.     } 

基于這種核心能力,輔以 Exceptional Continuations 特有的 try-catch、restore 等邏輯支持,Unwinder 能夠很好地實現 Continuation。不過后續作者并沒有再對其進行維護,同時它在異步操作方面的支持有一定缺陷,導致目前并不是非常流行。

Generator

另外一派是直接采用 Generator 來實現,這非常符合直覺,畢竟 Generator 就是一種轉移控制流的非常獨特的方式。

Yassine Elouafi 在系列文章 Algebraic Effects in JavaScript 中系統性地介紹了 Continuation、CPS、使用 Generator 改造 CPS 并實現 callCC、進一步支持 Delimited Continuation 以及最終支持 Algebraic Effects 等內容,行文順暢,內容示例夯實,是研究 JS Continuation 上乘的參考資料。

限于篇幅,本文不再對其原理進行深入介紹,感興趣的同學可以讀一下他的系列文章。下面是非常核心的 callcc 實現部分:   

  1. function callcc(genFunc) {  
  2.      return function(capturedCont) {  
  3.        function jumpToCallccPos(value) {  
  4.          return next => capturedCont(value);  
  5.        }  
  6.        runGenerator(genFunc(jumpToCallccPos), null, capturedCont);  
  7.      };  
  8.    } 

為了支持類似上文中提到的 Try-Catch,我們可以定義如下方法:   

  1. const handlerStack = [];  
  2.     function* trycc(computation, handler) {  
  3.       return yield callcc(function*(k) {  
  4.         handlerStack.push([handler, k]);  
  5.         const result = yield computation; 
  6.         handlerStack.pop();  
  7.         return result;  
  8.       });  
  9.     }  
  10.     function* throwcc(exception) { 
  11.       const [handler, k] = handlerStack.pop();  
  12.       const result = yield handler(exception); 
  13.       yield k(result); 
  14.     } 

從實現層面來看,Generator 方式比編譯方式更加簡單,核心代碼不到百行。但是因為 Generator 本身的認知復雜度導致一定門檻,另外所有調用 callCC 的相關代碼都必須使用 Generator 才能夠順利運行,這對于應用開發來說太過艱難,更不必說需要改造的海量的第三方模塊。

缺點

Continuation 并非銀彈,究其本質,它是一個高級版本的能夠處理函數表達式的 Goto 語句。眾所眾知,由于高度靈活導致的難以理解和調試,Goto 語句在各個語言中都屬于半封禁甚至封禁狀態。Continuation 面臨類似的窘境,需要使用者思慮周全,慎之又慎,將其應用控制在一定合理范圍,甚至像 React 這樣完全封裝在自身實現內部。

結語

Continuation 是個非常復雜的概念,為了能夠由淺入深、結合 JS 實際地來系統性闡述這一概念,筆者花費了自專欄開設以來最長的時間做各種梳理準備。不期望大家讀過這篇文章后就馬上開始使用 Continuation 或者 Algebraic Effects。如前文所述,目前 Continuation 還存在各方面的問題,應該實事求是,因地制宜,取其精華去其糟粕。正如 React Hooks、Suspense 一樣,它們并沒有真的搞了內部的編譯器或者引入 Generator,而是結合實際,神似而形不同,最大限度地滿足了設計目標。此外,期望這篇長文能幫助大家理解一些設計背后的思路,拓展一點前端工程師的技術視野,了解到整個編程領域內的優秀實踐。

彩蛋

React Fiber 是 React 16 引入的最為重要的底層變化,主要解決阻塞渲染的問題。為了實現這一目標,Fiber 化整為零,將組件中的每一個子組件或者子元素都視為一個 Fiber,通過類似 DOM Tree 的組織方式形成一個 Fiber Tree:

每個 Fiber 都有獨立的 render 過程和狀態存儲,在渲染時,我們可以把整個 Fiber Tree 的渲染過程理解成遍歷整個 Fiber Tree 的過程,每個 Fiber 的渲染工作可以理解為一個函數調用,為了不阻塞頁面交互,React 核心的任務調度算法是這樣的:   

  1. function workLoop(deadline) {  
  2.       let shouldYield = false 
  3.       while (nextUnitOfWork && !shouldYield) {  
  4.         nextUnitOfWork = performUnitOfWork 
  5.           nextUnitOfWork  
  6.         );  
  7.         shouldYield = deadline.timeRemaining() < 1 
  8.       }  
  9.       if (!nextUnitOfWork && wipRoot) {  
  10.         commitRoot();  
  11.       }  
  12.       requestIdleCallback(workLoop);  
  13.     }  
  14.     requestIdleCallback(workLoop); 

在每個瀏覽器 idle 的時間片內,workLoop 會盡可能多地執行 Fiber 渲染任務,如果時間到期且仍然有未完成任務時,nextUnitOfWork 會更新到最后一個待執行任務,然后等待下一個 idle 時間片繼續執行。

雖然這部分代碼并沒有明確地使用我們前文提到的種種 Continuation 方式,但是究其本質,React 是將 Fiber 引入之前的遞歸調用實現一次性完整渲染改變成以 Fiber Tree 為基礎的虛擬任務堆棧(或許不應該稱為棧,因為它是一個樹形結構),從而實現了對渲染任務的靈活調度。因此,nextUnitOfWork 在這里可以視作某種程度上的 Continuation,它代表著 React 渲染任務的“剩余部分”。

聯想到前面提到的 React Hooks、Suspense 背后借鑒的 Algebraic Effects 思想,難怪 React 團隊核心成員 Sebastian Markbåge 曾經放言:

React is operating at the level of a language feature 

 

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

2010-10-08 10:15:34

IFrameJS控件

2021-12-01 00:05:03

Js應用Ebpf

2021-09-17 09:30:57

鴻蒙HarmonyOS應用

2009-02-27 16:22:34

AjaxProAjax.NET

2023-03-24 09:07:22

SignalsJavaScript應用

2017-09-04 14:40:00

LimitLatchTomcat線程

2021-08-17 11:14:49

VoidJSTS

2022-06-30 08:58:09

時鐘輪RPC框架

2019-05-21 06:00:29

物聯網體育IOT

2017-10-27 16:19:23

語音識別CNN

2024-09-30 09:48:41

RabbitMQ消息中間件

2014-08-08 16:50:21

AB 測試安卓推送

2011-05-18 16:02:08

XML

2010-07-07 17:24:39

BGP協議

2021-12-07 18:35:08

物聯網執法應用IOT

2009-06-29 17:09:49

JavaBeanJSP

2022-06-28 08:02:44

SPISpringJava

2009-02-03 10:19:45

2009-06-25 15:54:18

設計模式EJB

2022-06-30 20:47:58

區塊鏈
點贊
收藏

51CTO技術棧公眾號

久久国产精品久久w女人spa| 麻豆视频在线免费看| 在线观看日批视频| 欧美第一精品| 欧美性猛交xxxxx水多| 蜜桃网站成人| av一级在线观看| 日韩精品久久| 日韩欧美国产三级电影视频| 影音先锋男人的网站| 性生活黄色大片| 国产精品一二| 中文字幕视频在线免费欧美日韩综合在线看 | 亚洲aaa激情| 免费在线看黄网址| gogo人体一区| 青青草97国产精品免费观看无弹窗版| 中文字幕一区日韩电影| 韩国三级hd中文字幕有哪些| 黑人巨大精品| 一区二区欧美在线观看| 欧洲高清一区二区| 丰满肉嫩西川结衣av| 免费高清不卡av| 国产91精品青草社区| 希岛爱理中文字幕| 精品国产一区二区三区av片| 亚洲精品一区二区三区精华液| 人妻丰满熟妇av无码区app| 国产福利在线免费观看| 国产精品家庭影院| 欧洲精品国产| 天天躁日日躁狠狠躁伊人| 国产麻豆91精品| 国产精品夫妻激情| 日本视频在线观看免费| 亚洲狠狠婷婷| 欧美激情欧美激情在线五月| 国产日韩欧美在线| 亚洲av无一区二区三区久久| 日韩高清在线| 欧美日韩在线看| 国产精品入口芒果| 日本理论片午伦夜理片在线观看| 国产精品久久久久久久裸模| 日韩三级电影免费观看| 四虎成人免费在线| av在线播放成人| 国产91一区二区三区| 国产强被迫伦姧在线观看无码| 久久精品国产秦先生| 国产精品r级在线| 中文字幕免费观看| 亚洲色图欧美| www.国产精品一二区| 女人十八毛片嫩草av| 国产精品一区二区av交换| 日韩成人中文字幕| theav精尽人亡av| 婷婷成人影院| 亚洲毛片一区二区| a级在线免费观看| 欧美日韩国产免费观看视频| 国产亚洲精品激情久久| 欧美波霸videosex极品| 欧美色图国产精品| 色偷偷88888欧美精品久久久| 在线视频这里只有精品| 婷婷亚洲五月| 欧美成年人视频| 久久精品国产亚洲av无码娇色 | 日韩性生活视频| 亚洲精品卡一卡二| 欧美a级一区| 欧美精品久久久久久久久久 | 欧美性xxxxxx少妇| 91看片破解版| 91精品短视频| 日韩精品中文字幕视频在线| 日本污视频网站| 亚洲在线久久| 91av中文字幕| 在线观看亚洲国产| 国产乱码精品一区二区三区av| 国产激情一区二区三区在线观看| 可以直接在线观看的av| 国产精品免费丝袜| 国产在线视频综合| 刘亦菲一区二区三区免费看| 欧美视频中文字幕| 男人的天堂免费| 九九久久婷婷| 欧美成人黑人xx视频免费观看| 亚洲 欧美 日韩 综合| 日韩精品欧美精品| 91精品入口蜜桃| 免费在线观看一级毛片| 国产精品福利一区二区三区| 日本欧美视频在线观看| 色婷婷综合久久久中字幕精品久久| 欧美欧美午夜aⅴ在线观看| 欧美双性人妖o0| 日韩欧美综合| 91成人天堂久久成人| 91丨porny丨在线中文| av在线不卡电影| 三上悠亚免费在线观看| 老司机2019福利精品视频导航| 7777精品伊人久久久大香线蕉经典版下载 | 精品国产三级| 亚洲天堂色网站| 久久久香蕉视频| 精品一区二区三区久久| 久热国产精品视频一区二区三区| 伊人在我在线看导航| 在线观看亚洲精品| 国产精品久久AV无码| 亚洲成人av| 国产精品久久久久av免费| 免费看日韩av| 中文字幕字幕中文在线中不卡视频| 日日摸日日碰夜夜爽无码| 中文幕av一区二区三区佐山爱| 亚洲精品影视在线观看| 精品无码人妻一区二区三| 日韩国产欧美在线视频| 国产精品一区二区不卡视频| 黄色网址在线免费| 欧美在线高清视频| 国产高清自拍视频| 激情综合久久| 91在线免费看片| 91在线中文| 欧美日韩小视频| a资源在线观看| 噜噜噜在线观看免费视频日韩 | 日本私人网站在线观看| 亚洲福利一二三区| 国产chinesehd精品露脸| 91精品国产91久久久久久密臀| 国产精品老女人视频| 免费a级毛片在线观看| 午夜在线成人av| 青青草视频网站| 亚洲高清毛片| 国产精品免费区二区三区观看| 蜜臀av在线播放| 精品区一区二区| 日韩黄色精品视频| 成人精品在线视频观看| 波多野结衣与黑人| www国产精品| 久久琪琪电影院| 免费国产羞羞网站视频| 亚洲成a人v欧美综合天堂下载| 免费看的av网站| 亚洲一级影院| 久久青青草原| 精品网站在线| 色噜噜狠狠狠综合曰曰曰| 在线免费观看一级片| 国产精品久99| 师生出轨h灌满了1v1| 亚洲黄色毛片| 欧美久久综合性欧美| 日韩毛片免费观看| 色播久久人人爽人人爽人人片视av| 91尤物国产福利在线观看| 国产精品日韩成人| 一级片免费在线观看视频| 欧美激情视频一区二区三区在线播放| 91国产丝袜在线放| 8x8ⅹ拨牐拨牐拨牐在线观看| 日韩精品视频在线观看网址| 91视频久久久| 自拍偷拍欧美激情| 美女伦理水蜜桃4| 玖玖玖国产精品| 中文字幕久精品免| 高清一区二区三区| 日本中文字幕成人| 黄在线免费看| 亚洲精品久久久久久久久久久久久| 最新中文字幕一区| 国产精品第四页| 天堂www中文在线资源| 玖玖精品视频| wwwwww欧美| 亚洲精品国产精品粉嫩| 国产精品专区一| 成年人黄色大片在线| 一色桃子一区二区| 亚洲女人18毛片水真多| 一本久久综合亚洲鲁鲁五月天 | 永久免费看mv网站入口78| 久久精品国产99国产| 国产 日韩 欧美在线| 精品国产精品久久一区免费式| 亚洲综合日韩中文字幕v在线| 日产福利视频在线观看| 日韩中文第一页| 无码精品黑人一区二区三区 | www.com黄色片| 亚洲视频中文| 一区二区av| 精品一区免费| 国产伦精品一区二区| 成人交换视频| 欧美一区二区色| 青草av在线| xxxx欧美18另类的高清| 天堂网在线资源| 欧美一区二区啪啪| 国产免费a视频| 亚洲第一搞黄网站| www日韩在线| 国产精品视频九色porn| 捆绑裸体绳奴bdsm亚洲| 国产激情精品久久久第一区二区 | 国产av第一区| 精品日韩免费| 欧美极品一区二区| 成人激情自拍| 99视频在线免费观看| 99riav视频一区二区| 亚洲18私人小影院| 在线中文字幕电影| 日韩视频在线免费| av网站大全在线观看| 亚洲人成网站777色婷婷| 日本黄色一区二区三区| 日韩天堂在线观看| 国产乱码一区二区| 欧美视频一区二| 久久久精品视频网站| 精品电影在线观看| 国产欧美日韩另类| 精品美女永久免费视频| 国产一级片免费观看| 一区二区三区在线观看网站| 成年人一级黄色片| 一区二区三区中文字幕精品精品| 国产尤物在线播放| 亚洲女女做受ⅹxx高潮| 婷婷在线精品视频| 伊人色综合久久天天| www.色小姐com| 亚洲欧美国产高清| 手机在线免费看毛片| 伊人色综合久久天天人手人婷| a级片在线观看免费| 一区二区三区免费网站| 久久久精品人妻一区二区三区四| 一区二区三区久久| 国产特黄大片aaaa毛片| 日韩欧美在线视频| 国产精品51麻豆cm传媒| 欧美精品久久一区| 精品久久国产视频| 亚洲国产精品嫩草影院久久| 天天操天天插天天射| 亚洲色图第三页| 日本中文字幕视频在线| 久久大大胆人体| 波多野在线观看| 欧美一级电影在线| 2019年精品视频自拍| 91久久精品日日躁夜夜躁国产| 日韩精品视频中文字幕| 国产区一区二区三区| 国产亚洲一卡2卡3卡4卡新区| 亚洲精品第一区二区三区| **女人18毛片一区二区| 日韩精品综合在线| 久久激情久久| 手机在线免费毛片| 91亚洲精品一区二区乱码| 国产一二三四五区| 亚洲天堂网中文字| 日韩成人一区二区三区| 欧美亚男人的天堂| 性一交一乱一色一视频麻豆| 日韩高清有码在线| 亚乱亚乱亚洲乱妇| 97精品一区二区三区| 欧美美女被草| 国产一区高清视频| 成人高清电影网站| 97免费视频观看| 日韩精品每日更新| 久草福利在线观看| 国产亚洲午夜高清国产拍精品| 侵犯稚嫩小箩莉h文系列小说| 欧美日韩免费看| 国产又粗又猛又爽又黄的视频一| 亚洲精品国产拍免费91在线| h视频网站在线观看| 国内精品久久久久久久| 美女久久久久久| 精品视频一区二区三区四区| 欧美独立站高清久久| 亚洲自偷自拍熟女另类| 国产中文字幕精品| 中文字幕第4页| 亚洲国产精品麻豆| 国产又粗又猛又黄又爽无遮挡| 精品一区精品二区| 国产蜜臀av在线播放| 国产美女直播视频一区| 嫩草国产精品入口| 国产手机视频在线观看| 日韩精品福利网| 国产伦精品一区二区三区88av| 中文字幕制服丝袜成人av| 成人毛片在线播放| 亚洲高清免费观看高清完整版| 麻豆系列在线观看| 国产福利视频一区二区| 国内毛片久久| 国产精品第157页| 狠狠网亚洲精品| 我不卡一区二区| 欧美午夜精品久久久久久久| 精品久久久中文字幕人妻| 色青青草原桃花久久综合| 在线黄色的网站| 国产嫩草一区二区三区在线观看| 一区二区三区网站| 午夜精品中文字幕| 亚洲国产岛国毛片在线| 在线免费观看av网址| 日韩经典中文字幕| 国产丝袜在线播放| 99porn视频在线| 欧美视频导航| av影片在线播放| 一区二区三区日本| 超碰在线观看99| 久久av在线看| 婷婷综合国产| www.激情网| 国产福利91精品一区| 欧美成人综合色| 精品少妇一区二区三区免费观看| 性欧美videoshd高清| 5g国产欧美日韩视频| 在线精品视频在线观看高清| 久久人人爽人人片| 亚洲一区二区三区四区五区中文| 朝桐光av在线一区二区三区| 欧美美最猛性xxxxxx| 91精品尤物| 欧美午夜性视频| 99久久精品免费看国产免费软件| 日韩 欧美 亚洲| 亚洲欧美日韩区| 电影一区电影二区| 亚洲无玛一区| 国产麻豆精品在线| 日韩欧美三级视频| 亚洲欧美视频在线| 91av一区| 国产免费内射又粗又爽密桃视频| 国产成人精品一区二| 久久久久久久伊人| 亚洲精品永久免费| 78精品国产综合久久香蕉| 手机成人av在线| 国产精品一区不卡| 一级片免费网址| 亚洲欧美在线磁力| 99精品美女视频在线观看热舞| 欧美人与动牲交xxxxbbbb| 99久久夜色精品国产网站| 亚洲 国产 日韩 欧美| 久久精品视频在线| 好吊妞视频这里有精品 | 久久99国产精品免费| 午夜少妇久久久久久久久| 日韩一区二区三区免费看 | 国产一区二区三区黄| 久久精品麻豆| 国产一二三区精品| 亚洲男人av在线| 久久精品一级| 日韩欧美xxxx| 一区二区成人在线视频 | 久久久精品免费| 久久精品凹凸全集| 手机在线国产视频| 精品电影在线观看| а√天堂在线官网| 久久影院理伦片| 国产精品一品二品| 欧美亚洲另类小说| 九九热这里只有精品免费看| 激情综合网五月| 国产精品久久久久久在线观看| 欧美主播一区二区三区美女| 波多野结依一区|