本文 AI 產出,尚未審核

JavaScript 陣列操作:sort / reverse / flat / flatMap

簡介

在日常的前端開發中,陣列是最常見的資料結構之一。無論是從 API 取得的清單、表單輸入的多筆資料,或是 UI 元件需要的排序與分層顯示,都離不開對陣列的操作。

本單元聚焦在四個非常實用的陣列方法——sortreverseflatflatMap。它們分別負責 排序反轉展平多層陣列,以及 映射後立即展平。掌握這四個方法,能讓你在處理資料時少寫大量迴圈與手動處理的程式碼,寫出更簡潔、更具可讀性的程式。

以下內容將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你深入了解這些 API,並提供實務應用的案例,幫助初學者到中階開發者快速上手。

核心概念

1. Array.prototype.sort() – 排序

sort直接在原陣列上進行排序(即「mutate」),預設會把元素轉成字串後以 Unicode 編碼排序,這往往不是我們想要的「數值」排序結果。

為什麼需要比較函式?

提供一個 比較函式 (compareFn) 可以自訂排序規則。比較函式必須回傳:

回傳值 意義
< 0 a 排在 b 前面
0 a 與 b 相等,順序不變
> 0 a 排在 b 後面

程式碼範例

// 1️⃣ 數值升冪(必須提供比較函式)
const numbers = [10, 2, 33, 4, 25];
numbers.sort((a, b) => a - b);
console.log(numbers); // [2, 4, 10, 25, 33]

// 2️⃣ 數值降冪
numbers.sort((a, b) => b - a);
console.log(numbers); // [33, 25, 10, 4, 2]

// 3️⃣ 依字母大小寫不敏感排序(localeCompare)
const fruits = ['Banana', 'apple', 'Orange', 'grape'];
fruits.sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
console.log(fruits); // ['apple', 'Banana', 'grape', 'Orange']

// 4️⃣ 物件陣列依屬性排序
const users = [
  { name: 'Alice', age: 28 },
  { name: 'Bob',   age: 22 },
  { name: 'Carol', age: 35 }
];
users.sort((x, y) => x.age - y.age);
console.log(users);
// [{name:'Bob',age:22},{name:'Alice',age:28},{name:'Carol',age:35}]

小提醒sort不穩定 的(在舊版 V8 中),但自 ES2019 起已成為穩定排序,相同鍵值的元素會保持原有相對順序。


2. Array.prototype.reverse() – 反轉

reverse 同樣是 原位(in‑place) 操作,會把陣列的順序顛倒。常與 sort 結合使用,快速取得 降冪最新的項目在最前 的效果。

程式碼範例

// 1️⃣ 基本使用
const letters = ['a', 'b', 'c', 'd'];
letters.reverse();
console.log(letters); // ['d', 'c', 'b', 'a']

// 2️⃣ 先排序再反轉 → 取得降冪
const scores = [85, 42, 97, 63];
scores.sort((a, b) => a - b).reverse();
console.log(scores); // [97, 85, 63, 42]

// 3️⃣ 反轉字串(先 split 再 reverse 再 join)
const str = 'JavaScript';
const reversed = str.split('').reverse().join('');
console.log(reversed); // 'tpircSavaJ'

注意reverse 會改變原陣列,若需要保留原始資料,請先 slice() 或使用展開運算子 [...] 產生副本。


3. Array.prototype.flat() – 展平(Flatten)

flat 用來把 多層巢狀陣列 拉平成一層(或指定深度)。默認深度為 1,若傳入 Infinity 則會把所有層級全部展平。

程式碼範例

// 1️⃣ 單層展平(預設深度 1)
const nested1 = [1, [2, 3], [4, [5]]];
console.log(nested1.flat()); // [1, 2, 3, 4, [5]]

// 2️⃣ 指定深度
console.log(nested1.flat(2)); // [1, 2, 3, 4, 5]

// 3️⃣ 完全展平
const deep = [1, [2, [3, [4, [5]]]]];
console.log(deep.flat(Infinity)); // [1, 2, 3, 4, 5]

// 4️⃣ 兼容稀疏陣列(空位會被移除)
const sparse = [1, , 2, [3, , 4]];
console.log(sparse.flat()); // [1, 2, 3, 4]

小技巧flat 只在 ES2019+ 環境支援,舊版瀏覽器可用 Array.prototype.concat.apply([], arr) 或手寫遞迴函式代替。


4. Array.prototype.flatMap() – 映射後展平

flatMap 結合了 map + flat(1) 的功能,先對每個元素執行映射函式,再把結果展平一層。它比先 mapflat 更有效率,因為只遍歷一次陣列。

程式碼範例

// 1️⃣ 基本範例:將每個字串切割成字元陣列,並展平
const words = ['hello', 'world'];
const chars = words.flatMap(word => word.split(''));
console.log(chars); // ['h','e','l','l','o','w','o','r','l','d']

// 2️⃣ 產生「展開」的座標資料
const points = [{x:1,y:2}, {x:3,y:4}];
const coordinates = points.flatMap(p => [p.x, p.y]);
console.log(coordinates); // [1,2,3,4]

// 3️⃣ 過濾與展平:只保留偶數,並展平
const numbers2 = [1, 2, 3, 4, 5];
const evens = numbers2.flatMap(n => (n % 2 === 0 ? [n] : []));
console.log(evens); // [2,4]

// 4️⃣ 與物件結合:把每筆訂單的商品展平成單一列表
const orders = [
  { id: 101, items: ['apple', 'banana'] },
  { id: 102, items: ['orange'] },
];
const allItems = orders.flatMap(o => o.items);
console.log(allItems); // ['apple','banana','orange']

差異說明:如果你使用 map 後接 flat,會產生兩次迭代;flatMap 只做一次迭代,效能上更佳,且語意更清晰。


常見陷阱與最佳實踐

陷阱 說明 建議的做法
陣列被原位改變 sortreverseflatflatMapflat)皆會直接改變原陣列。 若需保留原始資料,先 const copy = [...arr]; 再操作。
sort 直接比較字串 ["10","2"] 會得到 ["10","2"](字典序),而非數值升序。 使用比較函式 arr.sort((a,b)=>a-b);或先 map(Number)
比較函式忘記回傳數值 若回傳 true/false,排序結果不可預期。 確保 compareFn 回傳 負數、0、正數
flat 深度不夠 flat(1) 只會展平一層,深層仍保留。 依需求傳入正確的 depth,或 Infinity 完全展平。
稀疏陣列的空位 flat 會把空位視為 undefined 並移除,可能導致資料遺失。 若需要保留位置,可先 map 為明確值或使用 Array.from
flatMap 只能展平一層 flatMap 的展平深度固定為 1,無法一次展平多層。 需要多層展平時,先 mapflat(depth),或在 flatMap 內回傳已展平的子陣列。
使用舊版瀏覽器 flat / flatMap 於 ES2019 前不支援。 使用 Polyfill 或 Babel 轉譯。

最佳實踐

  1. 永遠先考慮不變性(immutability):在函式式程式設計中,盡量避免直接改變傳入的陣列,使用 slice()、展開運算子或 Array.from 產生新陣列。
  2. 明確寫比較函式:即使是字串陣列,也建議使用 localeCompare,避免因 Unicode 排序差異產生錯誤。
  3. 結合 filterflatMap:若需要同時過濾與展平,flatMap 可以直接回傳空陣列做「過濾」的動作,省去額外的 filter 步驟。
  4. 使用 Infinity 為深度時要小心記憶體:對超大或深度不確定的資料結構展平,可能產生巨大的陣列,需評估效能與記憶體限制。

實際應用場景

場景 可能使用的方法 為什麼適合
商品清單依價格排序 sort((a,b)=>a.price-b.price) 使用者常需要「由低到高」或「由高到低」的價格排列。
聊天訊息顯示最新在最上 messages.sort((a,b)=>b.time - a.time)messages.reverse()(若已是時間遞增) 讓 UI 一次渲染即可呈現正確順序。
多層分類資料展平成選項列表 categories.flat(depth)categories.flat(Infinity) 例如部落格的標籤、階層式目錄。
將 API 回傳的嵌套陣列轉成單一陣列 response.flatMap(item => item.subItems) 減少迴圈層次,直接得到所需的子項目集合。
文字搜尋的關鍵字切割 input.split(/\s+/).flatMap(word => word.split('')) 把使用者輸入的句子拆成字元或單詞,便於索引或高亮顯示。
生成座標點陣列供圖表繪製 data.flatMap(d => [d.x, d.y]) 一次取得所有 X、Y 值,直接傳給繪圖函式。
過濾並展平 API 回傳的錯誤訊息 `errors.flatMap(e => e.messages

總結

本篇文章從 排序 (sort)反轉 (reverse)展平 (flat)映射後展平 (flatMap) 四個核心 API 出發,說明了它們的行為特性、使用方式與常見的陷阱。掌握這些方法,你可以:

  • 快速整理資料:依需求排序、倒序或展平,使資料結構更符合前端顯示或後端傳遞的規格。
  • 寫出更簡潔的程式flatMap 讓映射與展平一步完成,減少迴圈與臨時陣列。
  • 避免副作用:了解哪些方法會「原位」改變陣列,適時使用副本保護資料完整性。

在實務開發中,這些陣列操作常與 filtermapreduce 搭配使用,形成強大的資料管線(pipeline)。只要遵循 不變性明確比較適度展平 的最佳實踐,你的程式碼將更具可讀性、維護性與效能。祝你在 JavaScript 陣列的世界裡玩得開心,寫出更優雅的程式!