Back to Blog
JavaScript Array Methods 完全指南:Mutating vs Non-Mutating
📝 Dev Notes

JavaScript Array Methods 完全指南:Mutating vs Non-Mutating

B
Blake
Dec 13, 2025 By Blake 11 min read
面試被問到 sort() 會改變原陣列嗎?一張表搞懂所有 Array Methods

前言:為什麼這很重要?

面試官:「sort() 會改變原陣列嗎?」

如果你不確定答案,這篇文章就是為你寫的。

在現代前端開發中,理解 Mutating(會改變原陣列) vs Non-mutating(不會改變原陣列) 不只是面試考題,更是寫出 bug-free 程式碼的關鍵:

  1. React State 更新:React 需要新的 reference 才會觸發 re-render

  2. 函數式編程:Immutability 是核心原則

  3. Debug 困難:意外的 mutation 是最難追蹤的 bug 之一


快速解答:sort() 會改變原陣列嗎?

const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.sort();

console.log(numbers); // [1, 1, 3, 4, 5] - 原陣列被改變了!
console.log(sorted);  // [1, 1, 3, 4, 5]
console.log(numbers === sorted); // true - 是同一個 reference!

答案是:會! sort() 是 Mutating method。


完整分類表:Mutating vs Non-mutating

Mutating Methods(會改變原陣列)

Method

說明

回傳值

push()

在尾端加入元素

新長度

pop()

移除尾端元素

被移除的元素

shift()

移除開頭元素

被移除的元素

unshift()

在開頭加入元素

新長度

splice()

刪除/插入元素

被刪除的元素陣列

sort()

排序

排序後的原陣列

reverse()

反轉

反轉後的原陣列

fill()

填充值

修改後的原陣列

copyWithin()

複製部分到另一位置

修改後的原陣列

Non-mutating Methods(不會改變原陣列)

Method

說明

回傳值

slice()

擷取部分陣列

新陣列

concat()

合併陣列

新陣列

map()

轉換每個元素

新陣列

filter()

過濾元素

新陣列

reduce()

累積運算

累積結果

find()

找到第一個符合的元素

元素或 undefined

findIndex()

找到第一個符合的索引

索引或 -1

includes()

檢查是否包含

boolean

indexOf()

找元素索引

索引或 -1

every()

是否全部符合

boolean

some()

是否有任一符合

boolean

flat()

攤平巢狀陣列

新陣列

flatMap()

map + flat

新陣列

join()

轉成字串

字串

toString()

轉成字串

字串

ES2023 新增:toSorted(), toReversed(), toSpliced()

// ES2023 新增了 Non-mutating 版本!
const numbers = [3, 1, 4];

// 舊方法(Mutating)
numbers.sort(); // 改變原陣列

// 新方法(Non-mutating)
const sorted = numbers.toSorted(); // 回傳新陣列,原陣列不變
const reversed = numbers.toReversed();
const spliced = numbers.toSpliced(1, 1, 'new');

重點深入:最常搞混的三組方法

1. splice() vs slice()

這是面試最愛考的!

const fruits = ['apple', 'banana', 'cherry', 'date'];

// slice() - Non-mutating(切片)
const sliced = fruits.slice(1, 3);
console.log(sliced);  // ['banana', 'cherry']
console.log(fruits);  // ['apple', 'banana', 'cherry', 'date'] - 沒變!

// splice() - Mutating(接合)
const removed = fruits.splice(1, 2, 'NEW');
console.log(removed); // ['banana', 'cherry'] - 被移除的元素
console.log(fruits);  // ['apple', 'NEW', 'date'] - 原陣列被改變!

記憶技巧

  • slice = 切片 → 切一塊出來,原本的還在

  • splice = 接合 → 要接就要改原本的

2. sort() 的陷阱

// 陷阱 1:預設是字串排序!
const numbers = [1, 10, 2, 21];
numbers.sort();
console.log(numbers); // [1, 10, 2, 21] - 不是你想的 [1, 2, 10, 21]!

// 正確做法:提供比較函數
const correct = [1, 10, 2, 21].sort((a, b) => a - b);
console.log(correct); // [1, 2, 10, 21]

// 陷阱 2:會改變原陣列
const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
console.log(original === sorted); // true!是同一個陣列

3. map() vs forEach()

const numbers = [1, 2, 3];

// map() - 回傳新陣列
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] - 沒變

// forEach() - 沒有回傳值
const result = numbers.forEach(n => n * 2);
console.log(result);  // undefined

React 實戰:如何正確更新 State 中的陣列

錯誤示範

function TodoList() {
  const [todos, setTodos] = useState(['Learn React', 'Learn TypeScript']);

  const addTodo = () => {
    // ❌ 錯誤:直接 mutate state
    todos.push('New Todo');
    setTodos(todos); // React 不會 re-render!因為 reference 沒變
  };

  const sortTodos = () => {
    // ❌ 錯誤:sort() 會改變原陣列
    setTodos(todos.sort()); // 雖然會 re-render,但這是反模式
  };
}

正確做法

function TodoList() {
  const [todos, setTodos] = useState(['Learn React', 'Learn TypeScript']);

  // ✅ 新增:使用展開運算符
  const addTodo = () => {
    setTodos([...todos, 'New Todo']);
  };

  // ✅ 刪除:使用 filter
  const removeTodo = (index) => {
    setTodos(todos.filter((_, i) => i !== index));
  };

  // ✅ 修改:使用 map
  const updateTodo = (index, newValue) => {
    setTodos(todos.map((todo, i) =>
      i === index ? newValue : todo
    ));
  };

  // ✅ 排序:先複製再排序
  const sortTodos = () => {
    setTodos([...todos].sort());
    // 或使用 ES2023
    // setTodos(todos.toSorted());
  };

  // ✅ 插入到特定位置:使用 slice + 展開
  const insertAt = (index, item) => {
    setTodos([
      ...todos.slice(0, index),
      item,
      ...todos.slice(index)
    ]);
  };
}

時間複雜度參考

Method

時間複雜度

說明

push()

O(1)

尾端操作很快

pop()

O(1)

尾端操作很快

shift()

O(n)

需要移動所有元素

unshift()

O(n)

需要移動所有元素

splice()

O(n)

最壞情況移動所有元素

slice()

O(n)

需要複製元素

map/filter/reduce

O(n)

遍歷所有元素

sort()

O(n log n)

排序演算法

find/findIndex

O(n)

最壞情況遍歷全部

includes/indexOf

O(n)

線性搜尋


記憶技巧 Cheat Sheet

口訣:「推拉接排反填」會改變

  • :push, unshift(推進去)

  • :pop, shift(拉出來)

  • :splice(接合)

  • :sort(排序)

  • :reverse(反轉)

  • :fill, copyWithin(填充)

簡單規則

  1. 回傳新陣列的 → 通常 Non-mutating(map, filter, slice, concat...)

  2. 回傳長度或被移除元素的 → 通常 Mutating(push, pop, splice...)

  3. 名字有 to 開頭的 → Non-mutating(toSorted, toReversed, toString...)


常見面試題

題目 1:以下程式碼的輸出是什麼?

const arr = [1, 2, 3];
const result = arr.push(4);
console.log(result);
console.log(arr);

答案

4        // push 回傳新長度
[1, 2, 3, 4]  // 原陣列被改變

題目 2:如何在不改變原陣列的情況下排序?

const numbers = [3, 1, 4, 1, 5];
// 請寫出程式碼

答案

// 方法 1:展開運算符
const sorted1 = [...numbers].sort((a, b) => a - b);

// 方法 2:slice()
const sorted2 = numbers.slice().sort((a, b) => a - b);

// 方法 3:ES2023 toSorted()
const sorted3 = numbers.toSorted((a, b) => a - b);

console.log(numbers); // [3, 1, 4, 1, 5] - 原陣列不變

題目 3:splice 和 slice 的差異?

答案

特性

splice()

slice()

Mutating

用途

刪除/插入元素

擷取子陣列

參數

(start, deleteCount, ...items)

(start, end)

回傳

被刪除的元素陣列

新的子陣列


總結

  1. Mutating methods 會改變原陣列:push, pop, shift, unshift, splice, sort, reverse, fill

  2. Non-mutating methods 回傳新陣列:slice, map, filter, concat, reduce...

  3. React State 永遠使用 Non-mutating 方式更新

  4. ES2023 新增了 toSorted(), toReversed(), toSpliced() 作為 Non-mutating 替代方案

  5. 不確定時,查文件或用 console.log 驗證!


延伸閱讀

Enjoyed this article? Show some love!

0
Clap

Enjoyed this article?

Subscribe for engineering notes and AI development insights

We respect your privacy. No spam, unsubscribe anytime.

Share this article

Comments