ES6系列--7. 可迭代協議和迭代器協議

ECMAScript 2015的幾個補充,並非新的內置或語法,而是協議。這些協議能夠被任何遵循某些約定的對象來實現。 有兩個協議:可迭代協議和迭代器協議。git

可迭代協議

可迭代協議容許 JavaScript 對象去定義或定製它們的迭代行爲, 例如(定義)在一個 for..of 結構中什麼值能夠被循環(獲得)。一些內置類型都是內置的可迭代對象而且有默認的迭代行爲, 好比 Array or Map, 另外一些類型則不是 (好比Object) 。數組

Iterator 接口的目的,就是爲全部數據結構,提供了一種統一的訪問機制,即for...of循環(詳見下文)。當使用for...of循環遍歷某種數據結構時,該循環會自動去尋找 Iterator 接口,調用Symbol.iterator方法,返回該對象的默認遍歷器。bash

ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可迭代的」(iterable)。Symbol.iterator屬性自己是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。數據結構

爲了變成可迭代對象, 一個對象必須實現(或者它原型鏈的某個對象)必須有一個名字是 Symbol.iterator 的屬性:函數

迭代器協議

該迭代器協議定義了一種標準的方式來產生一個有限或無限序列的值。oop

JavaScript 原有的表示「集合」的數據結構,主要是數組(Array)和對象(Object),ES6 又添加了MapSet。這樣就有了四種數據集合,用戶還能夠組合使用它們,定義本身的數據結構,好比數組的成員是MapMap的成員是對象。這樣就須要一種統一的接口機制,來處理全部不一樣的數據結構。ui

迭代器(Iterator)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。spa

Iterator 的做用有三個:一是爲各類數據結構,提供一個統一的、簡便的訪問接口;二是使得數據結構的成員可以按某種次序排列;三是 ES6創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。指針

Iterator 的遍歷過程是這樣的。code

  1. 建立一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。

  2. 第一次調用指針對象的next方法,能夠將指針指向數據結構的第一個成員。

  3. 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。

  4. 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。

每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含valuedone兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。

var someString = "hi";
typeof someString[Symbol.iterator]; // "function"

var iterator = someString[Symbol.iterator]();
iterator + "";    // "[object String Iterator]"

iterator.next()       // { value: "h", done: false }
iterator.next();      // { value: "i", done: false }
iterator.next();      // { value: undefined, done: true }
複製代碼

原生具有 Iterator 接口的數據結構以下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

注意對象是不具有 Iterator 接口的,一個對象若是要具有可被for...of循環調用的 Iterator 接口,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可)。

調用 Iterator 接口的場合

有一些場合會默認調用 Iterator 接口(即Symbol.iterator方法),除了下文會介紹的for...of循環,解構賦值, 擴展運算符其實也會調用默認的Iterator接口。

實際上,這提供了一種簡便機制,能夠將任何部署了 Iterator 接口的數據結構,轉爲數組。也就是說,只要某個數據結構部署了 Iterator 接口,就能夠對它使用擴展運算符,將其轉爲數組。

因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。下面是一些例子。

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

for...of

for...of 循環是最新添加到 JavaScript 循環系列中的循環。

它結合了其兄弟循環形式 for 循環和 for...in 循環的優點,能夠循環任何可迭代(也就是遵照可迭代協議)類型的數據。默認狀況下,包含如下數據類型:String、Array、MapSet,注意不包含 Object 數據類型(即 {})。默認狀況下,對象不可迭代。

在研究 for...of 循環以前,先快速瞭解下其餘 for 循環,看看它們有哪些不足之處。

for 循環

for 循環的最大缺點是須要跟蹤計數器和退出條件。咱們使用變量 i 做爲計數器來跟蹤循環並訪問數組中的值。咱們還使用 Array.length 來判斷循環的退出條件。

雖然 for 循環在循環數組時的確具備優點,可是某些數據結構不是數組,所以並不是始終適合使用 loop 循環。

for...in 循環

for...in 循環改善了 for 循環的不足之處,它消除了計數器邏輯和退出條件。可是依然須要使用 index 來訪問數組的值.

此外,當你須要向數組中添加額外的方法(或另外一個對象)時,for...in 循環會帶來很大的麻煩。由於 for...in 循環循環訪問全部可枚舉的屬性,意味着若是向數組的原型中添加任何其餘屬性,這些屬性也會出如今循環中。這就是爲什麼在循環訪問數組時,不建議使用 for...in 循環。

注意: forEach 循環 是另外一種形式的 JavaScript 循環。可是,forEach() 其實是數組方法,所以只能用在數組中。也沒法中止或退出 forEach 循環。若是但願你的循環中出現這種行爲,則須要使用基本的 for 循環。

for...of 循環

for...of 循環用於循環訪問任何可迭代的數據類型。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}
複製代碼

能夠隨時中止或退出 for...of 循環。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  if (digit % 2 === 0) {
    continue;
  }
  console.log(digit); //1,3,5,7,9
}
複製代碼

不用擔憂向對象中添加新的屬性。for...of 循環將只循環訪問對象中的值。

相關文章
相關標籤/搜索