每當你在陣列上串接 .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 介面幾乎與陣列方法相同。唯一的改變是:你要把鏈視為一個你正在描述的管道,而不是正在轉換的集合。
感謝閱讀!讓我們保持聯繫:
- ⭐ GitHub — 追蹤我並為專案按星: github.com/parsajiravand
- 💬 Discord — 加入前端最佳實踐社群: discord.gg/d9KRhuAwQ
- 📸 Instagram — 每日分享前端最佳實踐: @bestpractice___
- 💼 LinkedIn — linkedin.com/in/parsa-jiravand
- ✉️ Email(工作與合約詢問): [email protected]
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.