本文 AI 產出,尚未審核

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

簡介

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

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

以下內容將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你深入了解這些 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) 的功能,先對每個元素執行映射函式,再把結果展平一層。它比先 map 再 flat 更有效率,因為只遍歷一次陣列。

程式碼範例

// 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 只做一次迭代,效能上更佳,且語意更清晰。


常見陷阱與最佳實踐

陷阱 說明 建議的做法
陣列被原位改變 sort、reverse、flat、flatMap(flat)皆會直接改變原陣列。 若需保留原始資料,先 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,無法一次展平多層。 需要多層展平時,先 map 再 flat(depth),或在 flatMap 內回傳已展平的子陣列。
使用舊版瀏覽器 flat / flatMap 於 ES2019 前不支援。 使用 Polyfill 或 Babel 轉譯。

最佳實踐

  1. 永遠先考慮不變性(immutability):在函式式程式設計中,盡量避免直接改變傳入的陣列,使用 slice()、展開運算子或 Array.from 產生新陣列。
  2. 明確寫比較函式:即使是字串陣列,也建議使用 localeCompare,避免因 Unicode 排序差異產生錯誤。
  3. 結合 filter 與 flatMap:若需要同時過濾與展平,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 讓映射與展平一步完成,減少迴圈與臨時陣列。
  • 避免副作用:了解哪些方法會「原位」改變陣列,適時使用副本保護資料完整性。

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