迭代協議與生成器 101

Intro

首先關於這些名稱可能不少人都很模糊不清,儘管都是有關迭代遍歷但並不清楚彼此的定義和區別,那如今咱們就慢慢的解釋清楚。數組

terminology

那咱們優先從每個術語稱呼開始吧,來看看這些術語對應的中文名:bash

  • iterable object: [可]迭代對象異步

  • Iteration protocols: 迭代協議async

  • generator:生成器函數

Iteration protocols - 迭代協議

細心的同窗大概已經發現 Iteration protocols 是複數。是的在協議裏其實有2種協議分別是:工具

  • Iteration protocolsui

    • iterator protocol:迭代器協議this

    • iterable protocol:可迭代協議編碼

what are they?

在以往的JavaScripts中已經存在許多對集合類型對迭代方法,例如:for .. in, for 循環, map(), forEach()... 這些語法或者API都是有JavaScript內部實現如何進行迭代集合。例如:spa

  1. for .. in語法就是遍歷全部non-Symbol的可枚舉屬性
  2. map()API 就是對數組對索引依次遍歷獲得一個新數組;

那麼咱們如何對一個變量實現咱們自定義的迭代方式呢,這就須要依靠迭代協議。其實在我看來 迭代器協議 與 可迭代協議 是很是相似的以致於初探時後很難弄清,下面咱們分別來看看2個協議。

iterator protocol - 迭代器協議

image
😰😰😰

其實 迭代器協議 就是一個對象上定義了一個next屬性,而這個next屬性的定義有必定的要求,知足的話這就是一個實現了迭代器協議的對象,也被叫作 iterator : 迭代器

那麼這個next屬性有聲明要求呢?

  1. 首先 next 是一個方法,它不接受任何參數傳入;
  2. 其次調用next這個方法會返回一個對象,它包含2個屬性 value & done,其中 value表明本次迭代獲得的數據而 done用來表示迭代是否結束。例如:
    image
    以上 iterator 變量就是一個實現了迭代器協議的迭代器對象。

✌️PS:迭代器協議只是一種協議它指定了一個對象的迭代行爲控制(每次迭代返回值、迭代終點),但如何自動迭代運行還須要本身編碼實現(否則你得徹底編碼不停的iterator.next()、iterator.next()、iterator.next()),且每次迭代的上下文你得本身想辦法保留。

iterable protocol - 可迭代協議

image
🤬🤬🤬

其實可迭代協議是一個對象是擁有 @@iterator 屬性,而這個屬性鍵的定義來自 Symbol.iterator, 一樣@@iterator 屬性有必定要求,知足要求就實現了可迭代協議。

這些要求分別是:

  1. [Symbol.iterator](key name)屬性是一個方法,且不接受任何參數;
  2. 方法返回一個對象,這個對象就是迭代器協議對象。例如:
    image

這就是兩種迭代協議對內容與區別,那麼說完迭代協議咱們能夠來談談iterable object。

iterable object - [可]迭代對象

可迭代對象是對象上實現了 iterable protocol - 可迭代協議 的對象,且可使用build-ins語法進行迭代,例如 for (let i in iterable)[...iterable]。 ⚠️注意: 使用這些build-ins語法必須是對象上實現了可迭代協議不是迭代器協議,不然對對象迭代將會拋出異常:

❌ Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
    at <anonymous>:1:1
複製代碼

目前有不少JavaScript內置對數據集合已經實現了迭代器協議,有:

  1. Array
const iterable = [10, 20, 30];

for (const value of iterable) {
  console.log(value); // 10 20 30
}
複製代碼
  1. String
const iterable = 'boo';

for (const value of iterable) {
  console.log(value);  // 'b' 'o' 'o'
}
複製代碼
  1. TypedArray
  2. Map & Set
  3. fn arguments
  4. DOM collections

到此你可能發現了要實現自定義迭代行爲在寫法上仍是很複雜對,而且這裏存在兩種迭代協議,不一樣的庫或者工具可能選用某一種方法實現迭代對象的行爲,那麼就會可能形成不兼容。但因爲2中協議期本質又是十分相似全部咱們能夠創造一個同時知足迭代器協議和可迭代協議的對象,它相似:

var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}
複製代碼

這樣看起來仍是很複雜,因而有了咱們最後要說的 generator

generator - 生成器

generator對象generator函數 返回,它既符合[可]迭代協議,又符合迭代器協議,就像剛剛那種模版寫法。

它的寫法以下:

// 生成器函數
function* gen() { 
  yield 1;
  yield 2;
}

// 生成器對象
const g = gen();
複製代碼

JavaScript支持了生成器語法咱們就能夠更快的實現自定義的迭代對象了,例如上面的一個例子我用生成器實現是這樣的:

image

具體的 generator 語法再次再也不過多解釋,這就是 generator 與 itera... 之間的關係。

⚠️注意: 生成器對象不要重複使用 這句話什麼意思,咱們先來看一個MDN例子🌰:

const gen = (function *(){
  yield 1;
  yield 2;
  yield 3;
})();
for (const o of gen) {
  console.log(o);
  break;  // Closes iterator
}

// The generator should not be re-used, the following does not make sense!
for (const o of gen) {
  console.log(o); // Never called.
}
複製代碼

以上代碼咱們能夠知道 generator對象 就像是一個一次性消費品(一次性筷子🥢)被迭代行爲操做一次後將不會再次進行迭代。


基本上全部的東西就說完了,在補充說明最後一點東東

  1. 有了自定義迭代那麼如何實現 迭代流 實現 非阻塞代碼呢?在很早之前TJ大佬有實現一個庫CO就是幹這件事情的該庫在社區也比較流行。
  2. 迭代器是一個很好去寫異步代碼的方式,但在 es2017 async/await 語法糖的引入,是的異步代碼的編寫與閱讀更加方便。
相關文章
相關標籤/搜索