iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
1
Modern Web

從ES到ESNext - 30天輕鬆掌握ECMAScript系列 第 13

ES2015(ES6) - Iterable、Iterator、Generator

  • 分享至 

  • xImage
  •  

本系列文章經過重新編排和擴充,已出書為ECMAScript關鍵30天。原始文章因當時準備時程緊迫,多少有些許錯誤。為了避免造成讀者的困擾,以及配合書籍的內容規劃,將會陸續更新本系列文章。
本篇文章在 2021/11/8 已更新。

Iterable(可迭代的)

有關迭代的標準,主要依循以下幾個原則-

  • 有順序性地對資料集合中進行迭代。
  • 透過每次迭代回傳的結果取得目標值以外,也能知道是否還能進行下一次的迭代。
  • 對於取得迭代的目標值,有明確且一致的執行邏輯

可迭代的資料型態,物件本身或它的原型鏈中,必須實作迭代的執行方法,並且必須使用Symbol內建的常數-Symbol.iterator作為方法的鍵。

有些標準內建物件已經預設為Iterable,常見的有-

  • 字串
  • 陣列
  • Set
  • Map

for of

在前面介紹的陣列Set 跟 Map都有提到在 ES2015 中新增了這個迭代器。

為了要提供 Iterable 有一致的迭代方法,因此產生了 for ... of 的寫法。統整一下for of在遍歷常用的 Iterable 下的情境吧。

// String
const str = "12";
for (let v of str) {
  console.log(v);
}
// 1
// 2

// Array
const arr = ["a", "b"];
for (let v of arr) {
  console.log(v);
}
// a
// b

// Map
const map = new Map();
map.set(1, "a");
map.set(2, "b");
for (let v of map) {
  console.log(v);
}
// [1, "a"]
// [2, "b"]

// Set
const set = new Set([1, 1, "a", "b"]);
for (let v of set) {
  console.log(v);
}
// 1
// a
// b

// arguments
function fn(params) {
  for (let v of arguments) {
    console.log(v);
  }
}
fn("a", "b");
// a
// b

// NodeList
const nodeList = document.querySelectorAll("meta");
for (let v of nodeList) {
  console.log(v);
}
// <meta http-equiv=​"X-UA-Compatible" content=​"IE=edge,chrome=1">​
// <meta name=​"viewport" content=​"width=device-width">​

Iterator(迭代器)

當物件開始迭代後,就會呼叫物件中的@@iterator方法,並且透過執行後回傳的結果,取得每次的目標值和迭代狀態。一個標準的迭代器,至少要實作叫做next的方法,必且須符合以下條件-

  • 沒有參數的傳遞。
  • 具有done屬性,以布林值表示物件是否迭代完畢。
  • 具有value屬性,每次迭代後取得的目標值。

舉常用的標準內建物件-陣列,來使用它的迭代器來看看。

const names = ['Yuri', 'Ann', 'Joe'];
const iterator = names[Symbol.iterator]();

console.log(iterator.next()); // {value: 'Yuri', done: false}
console.log(iterator.next()); // {value: 'Ann', done: false}
console.log(iterator.next()); // {value: 'Joe', done: false}
console.log(iterator.next()); // { value: undefined, done: true}

Generator(產生器)

generator 是在 ES2015 中提出全新的函式類型。

在一般的函式,每次的調用都會從頭開始執行,並回傳一種結果,或是不回傳。而 generator 的機制就像上面提到的迭代器一樣,每次的函式調用,並不是單純執行函式,而是回傳 generator 物件,並從下一個指向的地方開始執行,並且回傳不同的執行結果。

function* fnName() {...}

要宣告 generator 的函式,必須以function開頭,並加上 *

function* generatorFn() {
  //...
}

yield & next()

在 generator 裡,以 yield的作陳述句的前綴字,在 generator 物件中,如果要取得下個陳述句的回傳結果,則執行物件的內建方法 next()

而回傳結果會被封裝成以下形式的物件:

{
  done: boolean, // 回傳是否以執行完return
  value: any  // 陳述句的執行結果
}
// 宣告 generator function
function* generatorFn() {
  yield 1;
  yield 2;
  return "done";
}
// 建立 generator 的物件
const gen1 = generatorFn();
const r1 = gen1.next(); // Object { done: false, value: 1 }
const r2 = gen1.next(); // Object { done: false, value: 2 }
const r3 = gen1.next(); // Object { done: true, value: "done" }

for ... of

在上面提到 generator 也是可迭代的物件之一。所以我們也可以使用 for ... of 來遍歷執行結果。

for (const v of generatorFn()) {
  console.log(v);
}
// 1
// 2

使用時機與延伸

  • 減少額外變數,降低記憶體使用:由 yield*進行委派,在這之後的產生器或可迭代物件執行迭代的行為
  • 改變執行結果:以 next 傳入值,取代上一個 yield 陳述式的執行結果
  • redux-saga:比起 以 promise 處理非同步的 redux-thunk的架構更加獨立,方便測試。也更貼近非同步存取資料的特性。

上一篇
ES2015(ES6) - 類別(class)
下一篇
ES2015(ES6) - Promise
系列文
從ES到ESNext - 30天輕鬆掌握ECMAScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言