每當你在陣列上串接 .filter().map() 時,JavaScript 都會建立兩個新的陣列。對小型列表來說,這幾乎感受不到。但對於大型資料集、產生值時才計算的產生器,或是只需要前幾個結果的串流,你卻會實體化數千個馬上就丟棄的元素。

迭代器輔助函式是延遲求值的替代方案。它們已原生支援於 Chrome 122、Firefox 131 與 Safari 18.2 — 無需額外函式庫、無需 polyfill、無需建置步驟。

「延遲求值」在實務上的意義

陣列方法是 立即求值:它們會立刻執行並回傳一個已完全填滿的新陣列。

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = numbers
  .filter(n => n % 2 === 0)  // → allocates [2, 4, 6, 8, 10]
  .map(n => n * n)            // → allocates [4, 16, 36, 64, 100]
  .slice(0, 3);               // → allocates [4, 16, 36]

Enter fullscreen mode Exit fullscreen mode

建立了三個陣列並隨即丟棄。只有最後一個陣列留存。

迭代器輔助函式是 延遲求值:它們會建立管道描述。在你索取元素前,不會處理任何元素。

const result = Iterator.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(3)
  .toArray();

// → [4, 16, 36]

Enter fullscreen mode Exit fullscreen mode

輸出結果相同,但沒有中間陣列。管道會逐一處理元素:1 經過 .filter() 失敗而被丟棄。2 通過 .filter()、經過 .map(),並計入 .take(3) 的數量。迴圈在取得三個符合條件的元素後就停止 — 其餘數字永遠不會被存取。

Iterator.from() 及其回傳值

Iterator.from() 會將任何可迭代物件(陣列、Set、Map、字串、產生器回傳值)包裝成一個附加了迭代器輔助方法的物件。

const set = new Set([1, 1, 2, 2, 3, 3, 4, 5]);
const result = Iterator.from(set)
  .filter(n => n % 2 !== 0)
  .toArray();

// → [1, 3, 5]

Enter fullscreen mode Exit fullscreen mode

產生器是天作之合。產生器本來就會依需求產生值;迭代器輔助函式讓你可以在它們之上建立管道,而無需建立中間陣列。

function* naturals() {
  let n = 0;
  while (true) yield n++;
}

const firstFiveSquaredEvens = Iterator.from(naturals())
  .filter(n => n % 2 === 0)
  .map(n => n * n)
  .take(5)
  .toArray();

// → [0, 4, 16, 36, 64]

Enter fullscreen mode Exit fullscreen mode

你無法對無限產生器使用陣列方法 — .filter() 在陣列上必須走到結尾才能回傳任何結果。

完整方法集

迭代器輔助函式提供了十種方法。第一組用來建立管道(延遲求值);第二組用來執行管道(終端方法):

Lazy:     .map(fn)  .filter(fn)  .take(n)  .drop(n)  .flatMap(fn)
Terminal: .toArray()  .reduce(fn, init)  .find(fn)  .some(fn)  .every(fn)  .forEach(fn)

Enter fullscreen mode Exit fullscreen mode

呼叫延遲求值方法會回傳一個新的迭代器 — 此時尚未執行任何計算。呼叫終端方法則會將值拉過整個鏈,直到取得所需結果後才停止。

// .find() 會在第一個符合的項目停止;其餘資料永遠不會被存取
const firstLongWord = Iterator.from(wordList)
  .filter(w => w.startsWith('pre'))
  .find(w => w.length > 10);

Enter fullscreen mode Exit fullscreen mode

實務範例:處理大型列表

你正在渲染一個包含 10,000 筆紀錄的可篩選列表。使用者已輸入搜尋關鍵字並選擇分類。畫面會顯示前 50 筆符合的結果。

使用陣列方法:

const visible = allRecords
  .filter(r => r.category === selectedCategory)  // walks all 10,000
  .filter(r => r.name.includes(query))           // walks the filtered set
  .slice(0, 50);                                 // finally limits

Enter fullscreen mode Exit fullscreen mode

使用迭代器輔助函式:

const visible = Iterator.from(allRecords)
  .filter(r => r.category === selectedCategory)
  .filter(r => r.name.includes(query))
  .take(50)
  .toArray();

Enter fullscreen mode Exit fullscreen mode

迭代器版本會在取得 50 筆符合結果後立即停止。如果這 50 筆出現在前 200 筆紀錄中,剩下的 9,800 筆永遠不會被讀取。而陣列版本無論如何都會在第一次篩選時走訪每一筆紀錄。

什麼時候應該繼續使用陣列方法

迭代器輔助函式並非萬能的替代方案:

  • 你需要隨機存取:陣列允許依索引位置存取。迭代器只能向前移動一次。
  • 你需要多次走訪資料:迭代器只能被消耗一次。陣列可以重複使用。
  • 你需要 .sort():排序必須將完整資料集載入記憶體。沒有延遲求值的排序。
  • 列表很小:對十個項目而言,Iterator.from() 的額外開銷可以忽略。請選擇讀起來更清楚的方式。

最清楚的訊號:如果你在大型或可能無限的來源上串接陣列方法,且會丟棄中間結果,那就是迭代器輔助函式的使用情境。

TypeScript 支援

TypeScript 5.6 新增了 Iterator 全域型別與方法簽章。若你的 tsconfig.json 目標為 ES2024 或更新版本,它們會自動可用:

function* ids(): Generator<number> {
  let id = 0;
  while (true) yield id++;
}

const first10: number[] = Iterator.from(ids()).take(10).toArray();

Enter fullscreen mode Exit fullscreen mode

若使用較早的 lib 目標,你可能需要明確將 "ES2024" 加入 lib 陣列,或在進行 polyfill 時使用 @types/core-js

瀏覽器支援

迭代器輔助函式屬於 Baseline 2024:Chrome 122(2024 年 3 月)、Firefox 131(2024 年 9 月)、Safari 18.2(2024 年 12 月)、Node.js 22。對於舊版環境,core-js 3.38 以上版本已包含可加入 bundle 的 polyfill。

產生器函式本來就會回傳迭代器 — 若你今天正在撰寫產生器,當目標環境足夠新時,它們的回傳值會自動獲得這些方法。

🎮 親自試試

▶️ 開啟互動式遊樂場 →

直接在瀏覽器中執行 — 隨意操作並即時觀察概念的反應。

🧠 自我測驗

覺得已經懂了嗎?進行 8 題小測驗 →

即時回饋、每題都有提示,以及每個答案的說明 — 無論對錯。

重點整理

在你的程式碼中搜尋陣列上的 .filter().map().slice(0, n) 串接。每一個這樣的串接都會配置中間陣列,這些陣列隨即被垃圾回收器回收。將它們替換為 Iterator.from().take(n),管道就會在取得所需結果後立即停止 — 沒有多餘的工作,也沒有多餘的記憶體。API 介面幾乎與陣列方法相同。唯一的改變是:你要把鏈視為一個你正在描述的管道,而不是正在轉換的集合。


感謝閱讀!讓我們保持聯繫: