【譯】ES6中的 Iterators

【注】其實吸引個人是這張配圖。。。讓我想起了卷福劃的圈,有沒有~~javascript

咱們將在文章中分析一下Iterators(迭代器)。Iterators是JS中循環集合的一種新的方式。它是ES6中引入的,已經在不少場景中被普遍使用,所以已經變得很是受歡迎。html

簡介

若是你有這樣一個數組:java

const myFavouriteAuthors = [
  'Neal Stephenson',
  'Arthur Clarke',
  'Isaac Asimov', 
  'Robert Heinlein'
];
複製代碼

有時候,你想要獲取數組裏的每一個值,用來打印,操做,或者作一些其它邏輯。若是我問你怎麼作?你確定會說 —— 這很簡單。「用forwhile,for-of就好了」。實現將會是這樣:es6

for,while,for-of

設想一下,若是你須要自定義一下剛剛那個數組結構,好比保存做者信息,像這樣:數組

Custom Data Structure

如今,myFavouriteAuthors是一個對象了,包含了另外一個對象allAuthorsallAuthors又包含了3個數組,fictionscienceFiction以及fantasy。如今,若是我再問你如何經過循環myFavouriteAuthors來獲取全部做者,那你會怎麼來實現呢?你能夠繼續嘗試用複合循環來獲取全部數據。數據結構

不過,若是你這樣作:app

for (let author of myFavouriteAuthors) { 
  console.log(author)
}
// TypeError: {} is not iterable
複製代碼

你將會獲得TypeError說object不能被迭代。讓咱們看下可迭代是什麼意思,而且咱們如何才能讓這個object能夠迭代。在文章最後,你將會知道如何用for-of去迭代這個myFavouriteAuthorside

**Iterables(迭代) and Iterators(迭代器)函數

上一節咱們遇到了個問題。在咱們自定義的對象裏獲取全部做者信息不是那麼的方便。咱們須要某種方法,可以把全部(對象)內數據有順序的暴露出來。ui

讓咱們在myFavouriteAuthors里加一個方法getAllAuthors,用來返回全部做者,像這樣:

getAllAuthors

這是個簡單的實現。它完成了咱們的任務。然而,這樣會出現一些問題:

  • getAllAuthors這個命名太自由了。若是有些人有一個他們本身的myFavouriteAuthors,他們可能會取名叫retrieveAllAuthors
  • 做爲一名開發人員,咱們就總要去找這個用來獲取全部數據的方法名字。固然在這裏,它叫getAllAuthors
  • getAllAuthors 返回的是包含全部做者字符串的數組。但若是另一個開發人員返回了一個對象組成的數組,怎麼辦呢?

[ {name: 'Agatha Christie'}, {name: 'J. K. Rowling'}, ... ]

那麼開發人員必需要知道這個方法的準確名字以及返回的數據格式。

若是咱們定一個規則,方法的名字和返回類型是固定不可變的呢?

讓咱們把這個方法叫作 —— iteratorMethod

ECMA採起了相似的步驟來標準化這個循環自定義對象的過程。然而,ECMA用名爲Symbol.iterator來代替了iteratorMethod名字。Symbols提供了一個獨一無二的名字(惟一,而且不會和別的屬性名發生衝突)。Symbol.iterator將會返回一個叫作iterator的對象。這個iterator有一個next的方法,這個方法會返回一個用valuedone做爲key的對象。

這個叫value的key將包含當前值。它是任何類型的。這個done是一個布爾值。它表示是否已經獲取到了全部的值。

用一張圖來幫助說明一下 iterables, iterators , next 之間的關係。而 這個關係被稱爲 Iteration Protocol(迭代協議)

Relationship between iterables, iterators, and next.

按照Axel Rauschmayer的Exploring JS這本書所說:

  • 迭代是一種數據結構,能公開它的元素可訪問性。這個Symbol.iterator就是用來作這件事的方法。這個方法是一個 迭代器 工廠,也就是說,它將會建立一個 迭代器
  • iterator(迭代器) 是指向用來遍歷元素數據集的指針。

** 讓 objects 變得可迭代

咱們已經從上一節瞭解到,咱們須要實現一個叫作 Symbol.iterator 的方法。咱們將會使用computed property syntax(可計算的屬性語法)來設置這個key。示例以下:

Example of iterable

第四行,咱們定義了一個迭代器。是一個帶有next方法的對象。這個next方法返回的值取決於step變量。在25行,咱們獲取了這個iterator。27行,咱們調用next。咱們調用next屢次,直到done變成true

這正是在for-of循環裏發生的事情。for-of拿到這個對象的迭代器,而後循環調用next()直到done變成true。

JavaScript中的迭代

JS裏有不少可迭代的東西。可能不是顯而易見的,可是你仔細檢查一下,迭代器就會出來了。

這些是可迭代的:

  • Arrays and TypedArrays
  • Strings —— 能迭代出每一個字符或者Unicode碼
  • Maps —— 迭代出key-value對
  • Sets —— 迭代出元素
  • arguments —— 相似array
  • DOM元素 —— 正在開發中。。。

這些是會使用迭代器的:

  • for-of循環 —— 須要對象可迭代,不然會拋出一個TypeError

for (const value of iterable) { ... }

  • 數組的解構 —— 由於可迭代而發生瞭解構。讓咱們看下:
const array = ['a', 'b', 'c', 'd', 'e'];
const [first, ,third, ,last] = array;
複製代碼

這樣就等同於:

const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const first = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const third = iterator.next().value
iterator.next().value // Since it was skipped, so it's not assigned
const last = iterator.next().value
複製代碼
  • ...操做符

代碼以下:

const array = ['a', 'b', 'c', 'd', 'e'];
const newArray = [1, ...array, 2, 3];
複製代碼

等同於:

const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const newArray = [1];
for (let nextValue = iterator.next(); nextValue.done !== true; nextValue = iterator.next()) {
  newArray.push(nextValue.value);
}
newArray.push(2)
newArray.push(3)
複製代碼
  • Maps and Sets

Map的構造函數會迭代出[key,value]放進Map裏,Set的構造函數會迭代出元素放進Set裏:

const map = new Map([[1, 'one'], [2, 'two']]);
map.get(1) 
// one
const set = new Set(['a', 'b', 'c]);
set.has('c');
// true
複製代碼
  • 迭代器也是理解generator函數的前提

myFavouriteAuthors能夠迭代

直接上代碼:

const myFavouriteAuthors = {
  allAuthors: {
    fiction: [
      'Agatha Christie', 
      'J. K. Rowling',
      'Dr. Seuss'
    ],
    scienceFiction: [
      'Neal Stephenson',
      'Arthur Clarke',
      'Isaac Asimov', 
      'Robert Heinlein'
    ],
    fantasy: [
      'J. R. R. Tolkien',
      'J. K. Rowling',
      'Terry Pratchett'
    ],
  },
  [Symbol.iterator]() {
    // Get all the genre in an array
    const genres = Object.values(this.allAuthors);
    
    // Store the current genre and author index
    let currentAuthorIndex = 0;
    let currentGenreIndex = 0;
    
    return {
      // Implementation of next()
      next() {
        // authors according to current genre index
        const authors = genres[currentGenreIndex];
        
        // doNotHaveMoreAuthors is true when the authors array is exhausted.
        // That is, all items are consumed.
        const doNothaveMoreAuthors = !(currentAuthorIndex < authors.length);
        if (doNothaveMoreAuthors) {
          // When that happens, we move the genre index to the next genre
          currentGenreIndex++;
          // and reset the author index to 0 again to get new set of authors
          currentAuthorIndex = 0;
        }
        
        // if all genres are over, then we need tell the iterator that we 
        // can not give more values.
        const doNotHaveMoreGenres = !(currentGenreIndex < genres.length);
        if (doNotHaveMoreGenres) {
          // Hence, we return done as true.
          return {
            value: undefined,
            done: true
          };
        }
        
        // if everything is correct, return the author from the 
        // current genre and incerement the currentAuthorindex
        // so next time, the next author can be returned.
        return {
          value: genres[currentGenreIndex][currentAuthorIndex++],
          done: false
        }
      }
    };
複製代碼

引用:

原文

相關文章
相關標籤/搜索