精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

色免费在线视频| 成人激情黄色网| 无码人妻一区二区三区在线视频| 大片免费在线观看| 国产精品影视在线| 中文字幕欧美国内| 最新av免费在线观看| av免费网站在线观看| 国产成人在线看| 欧美最猛性xxxxx亚洲精品| 亚洲一级理论片| 露出调教综合另类| 欧美顶级少妇做爰| 国产精品免费观看久久| 黄色网页在线观看| 久久免费偷拍视频| 999视频在线观看| 久久影视中文字幕| 亚洲国产国产亚洲一二三| 国产视频亚洲视频| 香蕉视频在线观看黄| 高清成人在线| 婷婷综合另类小说色区| 异国色恋浪漫潭| 精品视频一二三| 懂色中文一区二区在线播放| 国产精品久久久久久久久久三级 | 久久久久久久久久美女| 999视频在线免费观看| 中文字幕你懂的| 国产精品主播| 97色在线视频观看| 九九视频免费看| 99久久婷婷这里只有精品 | 91九色在线免费视频| 欧美人一级淫片a免费播放| 亚洲精品一二| 欧美激情va永久在线播放| 亚洲色图100p| japanese国产精品| 精品亚洲男同gayvideo网站| 任你躁av一区二区三区| 亚洲一区有码| 欧美人与禽zozo性伦| 色综合天天色综合| 日韩国产网站| 欧美色图天堂网| 午夜激情福利在线| 性欧美hd调教| 色婷婷亚洲精品| 日韩中文字幕免费在线| 电影一区二区三区| 色综合一个色综合亚洲| 日日橹狠狠爱欧美超碰| 丝袜诱惑一区二区| 岛国av午夜精品| 日韩av黄色网址| 天堂中文最新版在线中文| 亚洲成人777| 国产特级淫片高清视频| 美女在线视频免费| 色欧美片视频在线观看| 免费大片在线观看| 91成人抖音| 欧美嫩在线观看| 日韩欧美理论片| 1769国产精品视频| 亚洲第一页在线| 国产美女喷水视频| 国产精品一区高清| 深夜福利91大全| 欧美精品久久久久性色| 欧美亚洲不卡| 欧美重口另类videos人妖| 国产第一页在线观看| 久久99最新地址| 97se国产在线视频| 水莓100在线视频| 国产调教视频一区| 三年中国中文在线观看免费播放| 大片免费在线看视频| 亚洲午夜视频在线| 国产欧美在线一区| 欧美v亚洲v综合v国产v仙踪林| 欧美精品 日韩| 成熟妇人a片免费看网站| 亚洲婷婷影院| 久久精品国亚洲| 国产成人自拍视频在线| 免费在线观看视频一区| 91最新在线免费观看| 人妻一区二区三区四区| 久久香蕉国产线看观看99| 日韩欧美三级电影| 性爱视频在线播放| 欧美丝袜美女中出在线| 亚洲欧美自拍另类日韩| 涩爱av色老久久精品偷偷鲁| 日韩成人中文字幕| 纪美影视在线观看电视版使用方法| 中文字幕免费一区二区| 538国产精品一区二区免费视频 | 亚洲成人精品av| 中字幕一区二区三区乱码| 欧美日韩精品免费观看视频完整| 日本一区二区在线播放| 国产高清视频免费| 国产午夜亚洲精品不卡| 国产a级黄色大片| 国产日本久久| 日韩电视剧免费观看网站| 国产老头老太做爰视频| 亚洲中字黄色| 动漫3d精品一区二区三区| 成人午夜电影在线观看| 午夜精品一区二区三区电影天堂| www.成年人| 视频一区欧美| 97涩涩爰在线观看亚洲| 瑟瑟视频在线免费观看| 91视频免费播放| 免费在线看黄色片| 性欧美video另类hd尤物| 亚洲精品永久免费精品| 国产亚洲精久久久久久无码77777| 奇米四色…亚洲| 牛人盗摄一区二区三区视频| 黑人精品视频| 欧美成人精品二区三区99精品| 国产7777777| 日韩高清在线电影| 欧美一区1区三区3区公司 | 蜜芽tv福利在线视频| 亚洲精品久久嫩草网站秘色| 久久6免费视频| 日韩高清欧美| 国产精品成人在线| 欧美黄色小说| 欧美特黄级在线| 亚洲狠狠婷婷综合久久久久图片| 韩日欧美一区| 国产精品播放| av电影免费在线看| 精品国产伦理网| 国产精品theporn动漫| 国产91精品精华液一区二区三区| 国产日韩欧美大片| 欧美日韩黄网站| 欧美日韩国产va另类| 精品人妻aV中文字幕乱码色欲| 最近日韩中文字幕| 色综合天天做天天爱| 久久久久久精| 午夜影院在线播放| 亚洲美女中文字幕| 久久国产黄色片| 久久在线免费观看| 欧美xxxxx在线视频| 国产午夜一区| 国产精品黄视频| 黄上黄在线观看| 欧美伊人精品成人久久综合97| 午夜时刻免费入口| 久久精品国产99久久6| 艳色歌舞团一区二区三区| 欧美xxxx网站| 九九精品在线播放| 手机在线不卡av| 欧美性猛交xxxx免费看| 久久成人激情视频| 久久爱另类一区二区小说| 男女爱爱视频网站| www.亚洲一二| 欧洲美女7788成人免费视频| 国产在线电影| 91精品国产美女浴室洗澡无遮挡| 久久久国产成人| 99re成人在线| 免费一级特黄录像| 欧美在线网站| 久久国产精品久久| 日韩一级二级| 美女黄色丝袜一区| 天堂在线观看av| 欧美影院一区二区三区| 91香蕉视频在线播放| 成人深夜福利app| 好男人www社区| 欧美在线看片| 欧洲国产精品| 亚洲精品a区| 日本最新高清不卡中文字幕| 日本在线免费| 亚洲国产精品专区久久| 中文字幕日日夜夜| 亚洲一区在线观看免费观看电影高清 | 欧美激情专区| 国产精品777777在线播放| 69av视频在线播放| 美女免费久久| 亚洲欧美中文字幕在线一区| 国产乱色精品成人免费视频| 婷婷久久综合九色综合伊人色| 欧美色图17p| 91香蕉视频mp4| 欧美一级免费在线| 三级欧美韩日大片在线看| 日韩人妻一区二区三区蜜桃视频| 日韩精选在线| 91成人在线看| 国产亚洲欧美日韩精品一区二区三区| 性色av一区二区三区红粉影视| 久久bbxx| 国产亚洲欧洲高清| 天堂中文字幕av| 日韩精品中文字幕在线一区| 性高潮视频在线观看| 精品高清美女精品国产区| 午夜国产福利一区二区| 欧美国产精品v| 国产精品无码一区二区三区免费| 国产成人鲁色资源国产91色综| 久久久国产欧美| 久久九九精品| 亚洲熟妇无码一区二区三区| 欧美精品观看| 男人的天堂视频在线| 久久精品国产99久久| 欧美人与物videos另类| 国产欧美自拍一区| 国产精华一区| 秋霞一区二区三区| 国产一区二区香蕉| 国产欧美自拍| 国产日韩在线视频| 国产激情欧美| 国产免费一区二区三区在线观看| 欧美gay视频| 日本最新高清不卡中文字幕| 色综合桃花网| 国产91精品视频在线观看| 岛国在线视频网站| 欧美精品videossex88| 欧美日韩色网| 欧美激情久久久| 99久久精品免费看国产小宝寻花 | 国产大学生自拍| 亚洲狼人国产精品| 天天干中文字幕| 亚洲综合男人的天堂| 欧美成人手机视频| 一区二区视频在线| 国产无码精品久久久| 亚洲成人免费看| 久久久久久久久久久久久久av| 五月激情综合色| 日本中文字幕在线| 色老汉一区二区三区| 中文字幕乱码无码人妻系列蜜桃| 在线观看91精品国产入口| 亚洲 小说区 图片区| 欧美日韩精品一区视频| 国产精品爽爽久久久久久| 欧美一二三四在线| 欧洲av在线播放| 亚洲精品中文字幕女同| 国产二区在线播放| x99av成人免费| 色屁屁www国产馆在线观看| 欧美激情综合色综合啪啪五月| 阿v视频在线观看| 热久久这里只有| 外国成人毛片| 国产精品成人观看视频免费| 欧美极品在线观看| 亚洲欧美日韩国产成人综合一二三区| 综合久久一区| 国产av天堂无码一区二区三区| 久久久亚洲人| 手机精品视频在线| 成人国产亚洲欧美成人综合网| 美女洗澡无遮挡| 亚洲精品久久久久久国产精华液| 国产精品一区二区6| 欧美丝袜第三区| 亚洲av少妇一区二区在线观看| 国产视频精品va久久久久久| 亚洲搞黄视频| 亚洲 日韩 国产第一| 成人自拍视频网| 国产精品免费看一区二区三区| 国产亚洲一区| 欧美黄色免费网址| 亚洲一区网站| 9191在线视频| 99国产精品国产精品久久| 少妇视频在线播放| 亚洲自拍偷拍九九九| 亚洲中文字幕无码爆乳av| 精品久久久久一区| 成人影院免费观看| 久久久亚洲欧洲日产国码aⅴ| 精品三区视频| 久久riav二区三区| 亚洲成av人片乱码色午夜| 爱福利视频一区二区| 国产美女精品一区二区三区| 无码国产69精品久久久久同性| 性国产高清在线观看| 亚洲黄在线观看| 久久bbxx| 国产精品久久网| 日本天堂一区| 久久精品xxx| 久久国产精品99精品国产| 性久久久久久久久久| 亚洲一区二区三区小说| 91国内精品久久久| 国产午夜精品一区理论片飘花| 精品日韩av| 91精品综合久久| 66久久国产| 一区二区三区 日韩| 久久久亚洲高清| 国产一级做a爱片久久毛片a| 欧美一级专区免费大片| 91官网在线| 国产成人激情小视频| 欧美美女黄色| 免费观看国产精品视频| 国产精品白丝jk黑袜喷水| 麻豆精品国产免费| 欧美在线你懂的| 国产裸舞福利在线视频合集| 欧美亚洲视频在线看网址| 久久香蕉网站| 亚洲不卡中文字幕无码| 成人av免费在线| 国产精品第九页| 精品成a人在线观看| 美女日批视频在线观看| 亚洲一区二区三区毛片| 66视频精品| 亚洲精品一二三四| 亚洲精品国产无天堂网2021| av中文字幕免费在线观看| 久久精品一偷一偷国产| 高清不卡一区| 日韩中文字幕亚洲精品欧美| 国产在线一区二区| 国产精品九九九九九九| 欧美成人免费网站| 大黄网站在线观看| 国产精品一区二区三区四区五区 | 亚洲日穴在线视频| 国产理论片在线观看| 久久久精品国产| 在线精品视频一区| 国产精品无码人妻一区二区在线| 99视频精品在线| 日韩欧美在线观看免费| 国产午夜精品美女视频明星a级| 成人日韩在线| 三年中国中文在线观看免费播放| 国产乱人伦精品一区二区在线观看| 免费视频网站www| 日韩av在线一区二区| 偷拍精品精品一区二区三区| 亚洲免费久久| 国产99精品国产| 国产又大又黄又粗| 中文字幕视频在线免费欧美日韩综合在线看 | 色综合天天综合狠狠| 国产黄在线观看免费观看不卡| 国产日韩换脸av一区在线观看| 五月激情久久久| 中文字幕在线永久| 日本乱码高清不卡字幕| 日本视频在线免费观看| 国产精品国色综合久久| 国产精品婷婷| 在线观看黄网址| 亚洲国产成人91精品| 福利一区二区三区视频在线观看| 天天在线免费视频| 99久久久精品| 一级黄色片在线播放| 国外成人在线视频| 精品美女久久久| 欧美激情 亚洲| 欧美三级在线看| √8天堂资源地址中文在线| 日韩欧美精品一区二区三区经典| 国产乱人伦偷精品视频不卡 | 欧美浪妇xxxx高跟鞋交| а√天堂中文在线资源8| 亚洲精品中文字幕在线| kk眼镜猥琐国模调教系列一区二区| 自拍偷拍福利视频| 久久全国免费视频|