飯糰
ONIGIRI常駐監工
主樓2026-08-14
飯糰

Haru Li

前端工程師

發表 14 篇

·約 2 分鐘效能JavaScript量測

在 map 裡呼叫 find,是一行程式碼的四倍成長

這段程式碼幾乎每個專案都出現過:

{orders.map((order) => (
  <Row key={order.id} name={users.find((u) => u.id === order.userId)?.name} />
))}

看起來很無辜。它是我做這十個 demo 裡,差距最誇張的一個。

發想:從一個被砍掉的題目換來的

這題原本不在清單裡。我本來要做 useTransition,但量下來開跟關都是 1–8 ms,完全沒差——它真正的價值是「輸入框不會頓」的體感,那在自動化量測裡抓不到。

換成 Map 查表對上陣列 find 之後,數字自己會說話。

測試:兩種做法,結果互相驗證

demo 做的事是「把每筆訂單對應到使用者名稱」。兩種做法:

  • map + find:每筆訂單都從頭掃一次 users 陣列
  • 先建 Map 索引:花一次把陣列整理成「id → 使用者」的對照表,之後直接取

兩種做法的結果會互相比對驗證,確保比的是同一件事——不然很容易做出一個「比較快但算錯」的版本。

資料量可以調。2,000 筆時差 38 倍,5,000 筆時差 168 倍

demo 上還畫了一個示意動畫:某一筆訂單要找 user-N,左邊一格一格往下比對,右邊索引直接命中。

理解:為什麼是四倍成長

比對次數才是關鍵。5,000 筆的情境下:

做法 比對次數
map + find 2,001,000
先建 Map 4,000

find 平均要掃過陣列的一半才找到,所以 n 筆訂單 × m 位使用者大約是 n × m / 2 次比對。n 和 m 一起翻倍,乘積就變四倍。

Map 那側是 O(n + m):建表掃一次 m,查表 n 次、每次 O(1)。翻倍就是翻倍。

這就是為什麼它在小資料量時完全看不出來——100 筆的時候兩邊都是零點幾毫秒。等到資料長到幾千筆,那個平方項才突然咬人。

用個比喻:find 是每次查電話都從第一頁翻起;Map 是先按姓氏排好,之後直接翻到那一頁。排序花一次工,之後每次都省。

講解:怎麼發現與怎麼改

怎麼發現:在 mapforEachfilter 的回呼裡看到 findindexOfincludessome,就是這個模式。它藏得很好,因為那行程式碼讀起來完全合理。

怎麼改:把查找的那份資料先轉成 Map,放在迴圈外面。

const userById = new Map(users.map((u) => [u.id, u]));
// 迴圈裡
userById.get(order.userId)?.name

在 React 裡通常還會用 useMemo 包住建表那一步,避免每次渲染都重建。

什麼時候不用改:資料量確定很小、而且不會長大。為了 20 筆資料建 Map 只是讓程式變難讀。判準跟其他效能問題一樣——量得出來才動手


這個 demo 可以在 效能實驗室 自己操作。

這裡的文章都寫在自家 repo 的 markdown 檔裡,發文就是新增一個檔案。

© 2026 HARU LI