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

前端人不了解的迭代器和生成器

開發 前端
迭代器只需要知道集合中的當前位置,而其他循環則需要預先加載整個集合才能循環遍歷它。迭代器使用 next() 方法訪問集合中的下一個元素。但是,為了使用迭代器,值或數據結構應該是可迭代的。數組、字符串、映射、集合是 JavaScript 中的可迭代對象。普通對象是不可迭代的。

迭代器和生成器是 ES6 中引入的特性。迭代器通過一次消費一個項目列表來提高效率,類似于數據流。生成器是一種能夠暫停執行的特殊函數。調用生成器允許以塊的形式(一次一個)生成數據,而無需先將其存儲在列表中。下面就來深入理解 JavaScript 中的迭代器和生成器,看看它們是如何使用的,又有何妙用!

迭代器

JavaScript 中的迭代器可以分別兩種:同步迭代器和異步迭代器。

1. 同步迭代器

(1)迭代器和可迭代對象

在 JavaScript 中有很多方法可以遍歷數據結構。例如,使用 for 循環或使用 while 循環。迭代器具有類似的功能,但有顯著差異。

迭代器只需要知道集合中的當前位置,而其他循環則需要預先加載整個集合才能循環遍歷它。迭代器使用 next() 方法訪問集合中的下一個元素。但是,為了使用迭代器,值或數據結構應該是可迭代的。數組、字符串、映射、集合是 JavaScript 中的可迭代對象。普通對象是不可迭代的。

(2)定義迭代器

下面來看看集合不可迭代的場景:

const favouriteMovies = {
  a: '哈利波特',
  b: '指環王',
  c: '尖峰時刻',
  d: '星際穿越',
  e: '速度與激情',
}

這個對象是不可迭代的。如果使用普通的 for 循環遍歷它,就會拋出錯誤。隨著 ES6 中迭代器的引入,可以將其轉換為可迭代對象以便遍歷它。這些稱為自定義迭代器。下面看看如何實現對象的遍歷并打印出來:

favouriteMovies[Symbol.iterator] = function() {
  const ordered = Object.values(this).sort((a, b) => a - b);
  let i = 0;
  return {
    next: () => ({
      done: i >= ordered.length,
      value: ordered[i++]
    })
  }
}

for (const v of favouriteMovies) {
  console.log(v);
}

輸出結果如下:

哈利波特
指環王
尖峰時刻
星際穿越
速度與激情

這里使用 Symbol.iterator() 來定義迭代器。任何具有 Symbol.iterator 鍵的結構都是可迭代的。

可迭代對象具有以下行為:

  1. 當 for..of 循環開始時,它首先查找錯誤。如果未找到,則它會訪問方法和定義該方法的對象。
  2. 以 for..of 循環方式迭代該對象。
  3. 使用該輸出對象的 next() 方法來獲取要返回的下一個值。
  4. 返回的值的格式為 done:boolean, value: any。返回 done:true 時循環結束。

下面來創建一個 LeapYear 對象,該對象返回范圍為 (start, end) 的閏年列表,并在后續閏年之間設置間隔。

class LeapYear {
  constructor(start = 2020, end = 2040, interval = 4) {
    this.start = start;
    this.end = end;
    this.interval = interval;
  }
  [Symbol.iterator]() {
    let nextLeapYear = this.start;
    return {
      next: () => {
        if (nextLeapYear <= this.end) {
          let result = { value: nextLeapYear, done: false };
          nextLeapYear += this.interval;
          return result;
        }
        return { value: undefined, done: true };
      },
    };
  }
}

在上面的代碼中,為自定義類型 LeapYear 實現了 Symbol.iterator() 方法。分別在 this.start 和 this.end 字段中有迭代的起點和終點。使用 this.interval來跟蹤迭代的第一個元素和下一個元素之間的間隔。

現在,可以在自定義類型上調用 for...of 循環,并查看其行為和輸出值,就像默認數組類型一樣:

let leapYears = new LeapYear();
for (const leapYear of leapYears) {
    console.log(leapYear);
}

輸出結果如下:

2020
2024
2028
2032
2036
2040

這里的 LeapYear 通過 Symbol.iterator() 變成了可迭代對象。

在一些情況下,迭代器會比普通迭代更好。例如,在沒有隨機訪問的有序集合(如數組)中,迭代器的性能會更好,因為它可以直接根據當前位置檢索元素。但是,對于無序集合,由于沒有順序,就不會體驗到性能上的重大差異。

使用普通循環算法,例如 for 循環或 while 循環,您只能循環遍歷允許迭代的集合:

const favourtieMovies = [
  '哈利波特',
  '指環王',
  '尖峰時刻', 
  '星際穿越',
  '速度與激情',
];

for (let i=0; i < favourtieMovies.length; i++) {
  console.log(favouriteMovies[i]);
}

let i = 0;
while (i < favourtieMovies.length) {
  console.log(favourtieMovies[i]);
  i++;
}

由于數組是可迭代的,因此可以使用 for 循環遍歷。我們也可以為上面實現一個迭代器,這將允許更好地訪問基于當前位置的元素,而無需加載整個集合。代碼如下:

const iterator = favourtieMovies[Symbol.iterator]();
 
iterator.next();  // { value: '哈利波特', done: false }
iterator.next();  // { value: '指環王', done: false }
iterator.next();  // { value: '尖峰時刻', done: false }
iterator.next();  // { value: '星際穿越', done: false }
iterator.next();  // { value: '速度與激情', done: false }
iterator.next();  // { value: undefined, done: true }

next() 方法將返回迭代器的結果。它包括兩個值;集合中的元素和完成狀態。可以看到,當遍歷完成后,即使訪問數組外的元素,也不會拋出錯誤。它只會返回一個具有 undefined 值和完成狀態為 true 的對象。

(3)使用場景

那為什么向自定義對象中添加迭代器呢?我們也可以編寫自定義函數來遍歷對象以完成同樣的事情。

實際上,迭代器是一種標準化自定義對象的優雅實現方式,它為自定義數據結構提供了一種在更大的 JS 環境中很好地工作的方法。因此,提供自定義數據結構的庫經常會使用迭代器。例如,  Immutable.JS 庫就使用迭代器為其自定義對象(如Map)。所以,如果需要為封裝良好的自定義數據結構提供原生迭代功能,就考慮使用迭代器。

2. 異步迭代器

JavaScript 中的異步迭代對象是實現 Symbol.asyncIterator 的對象:

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
 
  }
};

我們可以將一個函數分配給 [Symbol.asyncIterator] 以返回一個迭代器對象。迭代器對象應符合帶有 next() 方法的迭代器協議(類似于同步迭代器)。

下面來添加迭代器:

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
    let count = 0;

    return {
      next() {
        count++;
        if (count <= 3) {
          return Promise.resolve({ value: count, done: false });
        }

        return Promise.resolve({ value: count, done: true });
      }
    };
  }
};

這里用 Promise.resolve 包裝了返回的對象。下面來執行 next() 方法:

const go = asyncIterable[Symbol.asyncIterator]();

go.next().then(iterator => console.log(iterator.value));
go.next().then(iterator => console.log(iterator.value));

輸出結果如下:

1
2

也可以使用 for await...of 來對異步迭代對象進行迭代:

async function consumer() {
  for await (const asyncIterableElement of asyncIterable) {
    console.log(asyncIterableElement);
  }
}

consumer();

異步迭代器和迭代器是異步生成器的基礎,后面會介紹異步生成器。

生成器

JavaScript 中的生成器可以分別兩種:同步生成器和異步生成器。

1. 同步生成器

(1)基本概念

生成器是一個可以暫停和恢復并可以產生多個值的過程。JavaScript 中的生成器由一個生成器函數組成,它返回一個可迭代 Generator 對象。

生成器是對 JavaScript 的強大補充。它們可以維護狀態,提供一種制作迭代器的有效方法,并且能夠處理無限數據流,可用于在前端實現無限滾動等。此外,當與 Promises 一起使用時,生成器可以模擬 async/await 功能,這使我們能夠以更直接和可讀的方式處理異步代碼。盡管 async/await 是處理常見、簡單的異步用例(例如從 API 獲取數據)的一種更普遍的方式,但生成器具有更高級的功能。

生成器函數是返回生成器對象的函數,由 function 關鍵字后面跟星號 (*) 定義,如下所示:

function* generatorFunction() {}

有時,我們可能會在函數名稱旁邊看到星號,而不是 function 關鍵字,例如 function *generatorFunction(),它的工作原理是相同的,但 function* 是一種更廣泛接受的語法。

生成器函數也可以在表達式中定義,就像常規函數一樣:

const generatorFunction = function* () {}

生成器甚至可以是對象或類的方法:

// 生成器作為對象的方法
const generatorObj = {
  *generatorMethod() {},
}

// 生成器作為類的方法
class GeneratorClass {
  *generatorMethod() {}
}

下面的例子都將使用生成器函數聲明得語法。

注意:與常規函數不同,生成器不能使用 new 關鍵字構造,也不能與箭頭函數結合使用。

現在我們知道了如何聲明生成器函數,下面來看看生成器返回的可迭代生成器對象。

(2)生成器對象

傳統的 JavaScript 函數會在遇到return 關鍵字時返回一個值。如果省略 return 關鍵字,函數將隱式返回 undefined。

例如,在下面的代碼中,我們聲明了一個 sum() 函數,它返回一個值,該值是兩個整數參數的和:

function sum(a, b) {
  return a + b
}

調用該函數會返回一個值,該值是參數的總和:

const value = sum(5, 6) // 11

而生成器函數不會立即返回值,而是返回一個可迭代的生成器對象。在下面的例子中,我們聲明了一個函數并給它一個單一的返回值,就像一個標準的函數:

function* generatorFunction() {
  return 'Hello, Generator!'
}

當調用生成器函數時,它將返回生成器對象,我們可以將其分配給一個變量:

const generator = generatorFunction()

如果這是一個常規函數,我們希望生成器為我們提供函數中返回的字符串。然而,我們實際得到的是一個處于掛起狀態的對象。因此,調用生成器將提供類似于以下內容的輸出:

generatorFunction {<suspended>}
  [[GeneratorLocation]]: VM335:1
  [[Prototype]]: Generator
  [[GeneratorState]]: "suspended"
  [[GeneratorFunction]]: ?* generatorFunction()
  [[GeneratorReceiver]]: Window

函數返回的生成器對象是一個迭代器。迭代器是一個具有可用的 next() 方法的對象,該方法用于迭代一系列值。next() 方法返回一個對象,其包含兩個屬性:

  • value:當前步驟的值;
  • done:布爾值,指示生成器中是否有更多值。

next() 方法必須遵循以下規則:

  • 返回一個帶有 done: false 的對象來繼續迭代;
  • 返回一個帶有 done: true 的對象來停止迭代。

下面就來在生成器上調用 next() 并獲取迭代器的當前值和狀態:

generator.next()

這將得到以下輸出結果:

{value: "Hello, Generator!", done: true}

調用 next() 時的返回值為 Hello, Generator!,并且 done 的狀態為 true,因為該值來自關閉迭代器的返回值。由于迭代器完成,生成器函數的狀態將從掛起變為關閉。這時再次調用生成器將輸出以下內容:

generatorFunction {<closed>}

除此之外,生成器函數也有區別于普通函數的獨特特征。下面我們就來了解一下 yield 運算符并看看生成器如何暫停和恢復執行。

(3)yield 運算符

生成器為 JavaScript 引入了一個新的關鍵字:yield。**yield**** 可以暫停生成器函數并返回 **yield** 之后的值,從而提供一種輕量級的方法來遍歷值。**

在下面的例子中,我們將使用不同的值暫停生成器函數三次,并在最后返回一個值。然后將生成器對象分配給 generator 變量。

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'

  return 'Hello, Generator!'
}

const generator = generatorFunction()

現在,當我們在生成器函數上調用 next() 時,它會在每次遇到 yield 時暫停。done 會在每次 yield 后設置為 false,表示生成器還沒有結束。一旦遇到 return,或者函數中沒有更多的 yield 時,done 就會變為 true,生成器函數就結束了。

連續四次調用 next() 方法:

generator.next()
generator.next()
generator.next()
generator.next()

這些將按順序得到以下結果:

{value: "One", done: false}
{value: "Two", done: false}
{value: "Three", done: false}
{value: "Hello, Generator!", done: true}

next() 非常適合從迭代器對象中提取有限數據。

注意,生成器不需要 return;如果省略,最后一次迭代將返回 {value: undefined, done: true},生成器完成后對 next() 的任何后續調用也是如此。

(4)遍歷生成器

使用 next() 方法可以遍歷生成器對象,接收完整對象的所有 value 和 done 屬性。不過,就像 Array、Map 和 Set 一樣,Generator 遵循迭代協議,并且可以使用 for...of 進行迭代:

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'

  return 'Hello, Generator!'
}

const generator = generatorFunction()

for (const value of generator) {
  console.log(value)
}

輸出結果如下:

One
Two
Three

擴展運算符也可用于將生成器的值分配給數組:

const values = [...generator]

console.log(values)

輸出結果如下:

['One', 'Two', 'Three']

可以看到,擴展運算符和 for...of 都不會將 return 的值計入 value。

注意:雖然這兩種方法對于有限生成器都是有效的,但如果生成器正在處理無限數據流,則無法在不創建無限循環的情況下直接使用擴展運算符或 for...of。

我們還可以從迭代結果中解構值:

const [a, b, c]= generator;
console.log(a);
console.log(b);
console.log(c);

輸出結果如下:

One
Two
Three

(5)關閉生成器

如我們所見,生成器可以通過遍歷其所有值將其 done 屬性設置為 true 并將其狀態設置為 closed 。除此之外,還有兩種方法可以立即關閉生成器:使用 return() 方法和使用 throw() 方法。

使用 return(),生成器可以在任何時候終止,就像在函數體中的 return 語句一樣。可以將參數傳遞給 return(),或將其留空以表示未定義的值。

下面來創建一個具有 yield 值但在函數定義中沒有 return 的生成器:

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'
}

const generator = generatorFunction()

第一個 next() 將返回“One”,并將 done 設置為 false。如果在那之后立即在生成器對象上調用 return() 方法,將獲得傳遞的值并將 done 設置為 true。對 next() 的任何額外調用都會給出默認的已完成生成器響應,其中包含一個 undefined 值。

generator.next()
generator.return('Return!')
generator.next()

輸出結果如下:

{value: "Neo", done: false}
{value: "Return!", done: true}
{value: undefined, done: true}

return() 方法會強制生成器對象完成并忽略任何其他 yield 關鍵字。當需要使函數可取消時,這在異步編程中特別有用,例如當用戶想要執行不同的操作時中斷數據請求,因為無法直接取消 Promise。

如果生成器函數的主體有捕獲和處理錯誤的方法,則可以使用 throw() 方法將錯誤拋出到生成器中。這將啟動生成器,拋出錯誤并終止生成器。

下面來在生成器函數體內放一個 try...catch 并在發現錯誤時記錄錯誤:

function* generatorFunction() {
  try {
    yield 'One'
   yield 'Two'
  } catch (error) {
    console.log(error)
  }
}

const generator = generatorFunction()

現在來運行 next() 方法,然后運行 throw() 方法:

generator.next()
generator.throw(new Error('Error!'))

輸出結果如下:

{value: "One", done: false}
Error: Error!
{value: undefined, done: true}

使用 throw() 可以將錯誤注入到生成器中,該錯誤被 try...catch 捕獲并記錄到控制臺。

(6)生成器對象方法和狀態

下面是生成器對象的方法:

  • next():返回生成器中的后面的值;
  • return():在生成器中返回一個值并結束生成器;
  • throw():拋出錯誤并結束生成器。

下面是生成器對象的狀態:

  • suspended:生成器已停止執行但尚未終止。
  • closed:生成器因遇到錯誤、返回或遍歷所有值而終止。

(7)yield 委托

除了常規的 yield 運算符之外,生成器還可以使用 yield* 表達式將更多值委托給另一個生成器。當在生成器中遇到 yield* 時,它將進入委托生成器并開始遍歷所有 yield 直到該生成器關閉。這可以用于分離不同的生成器函數以在語義上組織代碼,同時仍然讓它們的所有 **yield** 都可以按正確的順序迭代。

下面來創建兩個生成器函數,其中一個將對另一個進行 yield* 操作:

function* delegate() {
  yield 3
  yield 4
}

function* begin() {
  yield 1
  yield 2
  yield* delegate()
}

接下來,遍歷 begin() 生成器函數:

const generator = begin()

for (const value of generator) {
  console.log(value)
}

輸出結果如下:

1
2
3
4

外部的生成器(begin)生成值 1 和 2,然后使用 yield* 委托給另一個生成器(delegate),返回 3 和 4。

yield* 還可以委托給任何可迭代的對象,例如 Array 或 Map。yield 委托有助于組織代碼,因為生成器中任何想要使用 yield 的函數也必須是一個生成器。

(8)在生成器中傳遞值

上面的例子中,我們使用生成器作為迭代器,并且在每次迭代中產生值。除了產生值之外,生成器還可以使用 next() 中的值。在這種情況下,yield 將包含一個值。

需要注意,調用的第一個 next() 不會傳遞值,而只會啟動生成器。為了證明這一點,可以記錄 yield 的值并使用一些值調用 next() 幾次。

function* generatorFunction() {
  console.log(yield)
  console.log(yield)

  return 'End'
}

const generator = generatorFunction()

generator.next()
generator.next(100)
generator.next(200)

輸出結果如下:

100
200
{value: "End", done: true}

除此之外,也可以為生成器提供初始值。下面來創建一個 for 循環并將每個值傳遞給 next() 方法,同時將一個參數傳遞給 inital 函數:

function* generatorFunction(value) {
  while (true) {
    value = yield value * 10
  }
}

const generator = generatorFunction(0)

for (let i = 0; i < 5; i++) {
  console.log(generator.next(i).value)
}

這將從 next() 中檢索值并為下一次迭代生成一個新值,該值是前一個值乘以 10。輸出結果如下:

0
10
20
30
40

處理啟動生成器的另一種方法是將生成器包裝在一個函數中,該函數將會在執行任何其他操作之前調用 next() 一次。

(9)async/await

async/await 使處理異步數據更簡單、更容易理解。生成器具有比異步函數更廣泛的功能,但能夠復制類似的行為。以這種方式實現異步編程可以增加代碼的靈活性。

下面來構建一個異步函數,它使用 Fetch API 獲取數據并將響應記錄到控制臺。

首先定義一個名為 getUsers 的異步函數,該函數從 API 獲取數據并返回一個對象數組,然后調用 getUsers:

const getUsers = async function () {
  const response = await fetch('https://jsonplaceholder.typicode.com/users')
  const json = await response.json()

  return json
}

getUsers().then((response) => console.log(response))

輸出結果如下:

圖片圖片

使用生成器可以創建幾乎相同但不使用 async/await 關鍵字的效果。相反,它將使用我們創建的新函數,并產生值而不是等待 Promise。

const getUsers = asyncAlt(function* () {
  const response = yield fetch('https://jsonplaceholder.typicode.com/users')
  const json = yield response.json()

  return json
})

getUsers().then((response) => console.log(response))

如我們所見,它看起來與 async/await 實現幾乎相同,除了有一個生成器函數被傳入以產生值。

現在可以創建一個類似于異步函數的 asyncAlt 函數。asyncAlt 有一個 generatorFunction 參數,它是產生 fetch 返回的 Promise 的函數。asyncAlt 返回函數本身,并 resolve 它得到的每個 Promise,直到最后一個:

function asyncAlt(generatorFunction) {
  return function () {
    // 創建并分配生成器對象
    const generator = generatorFunction()

    // 定義一個接受生成器下一次迭代的函數
    function resolve(next) {
      // 如果生成器關閉并且沒有更多的值可以生成,則解析最后一個值
      if (next.done) {
        return Promise.resolve(next.value)
      }

      // 如果仍有值可以產生,那么它們就是Promise,必須 resolved。
      return Promise.resolve(next.value).then((response) => {
        return resolve(generator.next(response))
      })
    }

    // 開始 resolve Promise
    return resolve(generator.next())
  }
}

這樣就會得到和async/await一樣的結果:

圖片圖片

盡管這個方法可以為代碼增加靈活性,但通常 async/await 是更好的選擇,因為它抽象了實現細節并讓開發者專注于編寫高效代碼。

(10)使用場景

很多開發人員認為生成器函數視為一種奇特的 JavaScript 功能,在現實中幾乎沒有應用。在大多數情況下,確實用不到生成器。

生成器的優點:

  • 惰性求值:除非需要,否則不計算值。它提供按需計算。只有需要它時,value 才會存在。
  • 內存效率高:由于惰性求值,生成器的內存效率非常高,因為它不會為預先生成的未使用值分配不必要的內存位置。
  • 更簡潔的代碼:生成器提供更簡潔的代碼,尤其是在異步行為中。

生成器在對性能要求高的場景中有很大的用處。特別是,它們適用于以下場景:

  • 處理大文件和數據集。
  • 生成無限的數據序列。
  • 按需計算昂貴的邏輯。

Redux sagas 就是實踐中使用的生成器的一個很好的例子。它是一個用于管理redux應用異步操作的中間件,redux-saga 通過創建 sagas 將所有異步操作邏輯收集在一個地方集中處理,可以用來代替 redux-thunk 中間件。

2. 異步生成器

ECMAScript 2018 中引入了異步生成器的概念,它是一種特殊類型的異步函數,可以隨意停止和恢復其執行。

同步生成器函數和異步生成器函數的區別在于,后者從迭代器對象返回一個異步的、基于 Promise 的結果。

要想創建異步生成器函數,需要聲明一個帶有星號 * 的生成器函數,前綴為 async:

async function* asyncGenerator() {

}

一旦進入函數,就可以使用 yield 來暫停執行:

async function* asyncGenerator() {
  yield 'One'
  yield 'Two'
}

這里 yield 會暫停執行并返回一個迭代器對象給調用者。這個對象既是可迭代對象,又是迭代器。

異步生成器函數不會像常規函數那樣在一步中計算出所有結果。相反,它會逐步提取值。我們可以使用兩種方法從異步生成器解析 Promise:

  • 在迭代器對象上調用 next();
  • 使用 for await...of 異步迭代。

對于上面的例子,可以這樣做:

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}

const go = asyncGenerator();

go.next().then(iterator => console.log(iterator.value));
go.next().then(iterator => console.log(iterator.value));

輸出結果如下:

'One';
'Two'

另一種方法使用異步迭代 for await...of。要使用異步迭代,需要用 async 函數包裝它:

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}

async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}

consumer();

for await...of 非常適合提取非有限數據流。

責任編輯:武曉燕 來源: 前端充電寶
相關推薦

2023-03-01 00:07:32

JavaScript迭代器生成器

2017-06-26 16:26:15

Python迭代對象迭代器

2023-11-15 13:35:00

迭代器生成器Python

2010-07-20 13:56:26

Python迭代器生成器

2024-11-11 06:10:00

Python生成器迭代器

2024-05-10 11:31:59

Python迭代器生成器

2010-08-19 10:12:34

路由器標準

2023-05-05 08:53:38

迭代器生成器Python

2011-03-29 15:44:41

對日軟件外包

2020-11-30 06:27:35

Java泛型Object

2020-04-20 10:55:57

大數據人工智能技術

2020-09-16 07:59:40

數組內存

2021-07-12 07:01:39

AST前端abstract sy

2019-04-03 09:10:35

Rediskey-value數據庫

2019-11-21 15:08:13

DevOps云計算管理

2014-06-16 10:03:54

分組交換

2020-07-07 07:34:29

RedisSDS數據結構

2023-02-12 21:54:32

架構AI元宇宙

2020-10-10 09:19:58

JavaScript開發技術

2024-11-01 15:51:06

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 大乳boobs巨大吃奶挤奶 | 亚洲一区二区在线视频 | 在线日韩不卡 | 国产精品1区2区 | 欧美成人免费 | 国产小视频在线 | 亚州精品天堂中文字幕 | 日韩一区二区三区在线播放 | 91色视频在线 | 精品国产一区二区三区久久 | 亚洲精品中文字幕 | 国产精品久久久久久久久免费软件 | 中文字幕高清视频 | 午夜精品一区二区三区在线 | 国产成人久久精品 | 欧美一级片在线看 | 国产a爽一区二区久久久 | 久久九九免费 | www久久av| 在线亚洲电影 | 国产高清在线精品一区二区三区 | 亚洲一区亚洲二区 | 国产一区二区三区亚洲 | 午夜激情视频在线 | 福利视频三区 | 北条麻妃99精品青青久久 | 久久精品国产精品青草 | 毛片免费看的 | 精品国产一区二区三区免费 | 欧美成人一区二区三区 | 欧美精品在线观看 | 日韩成人免费av | 日韩在线| 国产一区二区电影 | 国产亚洲网站 | 成人在线观看免费观看 | 超碰520 | 狠狠的干狠狠的操 | 久热精品在线 | 中文字幕综合在线 | 麻豆毛片 |