JavaScript 陣列操作(Arrays)── push / pop / shift / unshift
簡介
在 JavaScript 中,陣列 (Array) 是最常用的資料結構之一。它不僅能儲存同類型或不同類型的資料,還提供了一組直覺且高效的 API,讓我們可以輕鬆地在陣列的兩端加入或移除元素。
本單元聚焦於四個最基礎、最常被使用的操作:push、pop、shift 與 unshift。掌握它們不只讓程式碼更簡潔,也能避免許多因手動管理索引而產生的錯誤。
為什麼要特別學習這四個方法?
- 可讀性:使用語意化的 API(例如
push代表「推入」),比手動調整length或使用array[array.length] = x更易懂。- 效能:在大多數瀏覽器實作中,這四個方法已經過最佳化,尤其在 O(1) 時間複雜度下完成操作。
- 實務需求:從資料佇列、堆疊到 UI 的動態渲染,都會頻繁用到這些方法。
以下將逐一說明概念、提供實作範例,並討論常見陷阱與最佳實踐,最後以真實案例說明它們在專案中的應用方式。
核心概念
1. push – 從陣列尾端加入元素
push 會把一個或多個元素 加到陣列的最後面,同時回傳 新陣列的長度。
// 範例 1:基本使用
let fruits = ['apple', 'banana'];
let newLength = fruits.push('orange'); // -> ['apple', 'banana', 'orange']
console.log(newLength); // 3
// 範例 2:一次加入多個元素
fruits.push('grape', 'melon');
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'melon']
小技巧:如果你只想取得加入後的最後一個值,可以直接使用
fruits[fruits.length - 1]。
2. pop – 從陣列尾端移除元素
pop 會 移除陣列最後一個元素,並回傳被移除的值。如果陣列為空,回傳 undefined。
// 範例 3:移除最後一個元素
let last = fruits.pop(); // 移除 'melon'
console.log(last); // 'melon'
console.log(fruits); // ['apple', 'banana', 'orange', 'grape']
// 範例 4:與 while 迴圈搭配,清空陣列
while (fruits.length) {
console.log('pop:', fruits.pop());
}
// 輸出依序為: 'grape', 'orange', 'banana', 'apple'
注意:
pop會改變原始陣列 (mutate),如果你在函式式編程中想保留原始資料,請先slice()或使用展開運算子...複製一份。
3. shift – 從陣列頭部移除元素
shift 會 移除陣列的第一個元素,其餘元素向前移動,並回傳被移除的值。此操作的時間複雜度為 O(n),因為每個元素都需要重新定位。
// 範例 5:從頭部移除
let numbers = [10, 20, 30, 40];
let first = numbers.shift(); // 移除 10
console.log(first); // 10
console.log(numbers); // [20, 30, 40]
最佳實踐:若你需要頻繁在頭部插入/移除,考慮使用 雙端佇列 (Deque) 或
Array.prototype.unshift搭配Array.prototype.pop組成的環形緩衝區。
4. unshift – 從陣列頭部加入元素
unshift 會 在陣列的最前面插入一個或多個元素,其餘元素向後移動,回傳 新陣列的長度。同樣是 O(n) 的操作。
// 範例 6:在頭部加入元素
let queue = ['b', 'c'];
let newLen = queue.unshift('a'); // -> ['a', 'b', 'c']
console.log(newLen); // 3
console.log(queue); // ['a', 'b', 'c']
// 範例 7:一次加入多個元素
queue.unshift('x', 'y');
console.log(queue); // ['x', 'y', 'a', 'b', 'c']
提醒:在大量資料的情況下,頻繁使用
unshift會產生較大的記憶體搬移成本,請斟酌是否真的需要「頭部」插入。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式或最佳實踐 |
|---|---|---|
忘記 push、unshift 會回傳新長度 |
直接把回傳值當作陣列使用,導致錯誤 | 只在需要長度時才使用回傳值,否則直接操作原陣列 |
在迴圈內同時使用 push 與 pop |
可能不小心改變迭代次數,導致漏掉元素 | 使用 while (arr.length) 或先複製陣列再迭代 |
誤以為 shift/unshift 為 O(1) |
大型陣列會出現性能瓶頸 | 若需求是高效佇列,考慮 Array.prototype.slice、LinkedList 或第三方庫(如 deque) |
| 直接改變參數傳入的陣列 | 造成副作用,特別在函式式編程中不易追蹤 | 使用 const newArr = [...oldArr, newItem](等價於 push)或 oldArr.slice() 複製 |
使用 pop/shift 取得值後忘記檢查 undefined |
空陣列時會得到 undefined,但程式可能繼續執行 |
在使用前先檢查 if (arr.length) { ... } |
小技巧與最佳寫法
一次加入多個元素
push、unshift都支援傳入多個參數,省去多次呼叫的開銷。// 同時加入三筆資料 stack.push(1, 2, 3);結合展開運算子建立新陣列(避免副作用)
const original = [1, 2]; const added = [...original, 3]; // 等同於 original.push(3) 但不改變 original使用
Array.from或Array.prototype.slice複製const copy = original.slice(); // 或 Array.from(original)在需要「先入先出」的佇列時,使用
push+shiftconst queue = []; queue.push('first'); queue.push('second'); const processed = queue.shift(); // 'first'
實際應用場景
1. 瀏覽器歷史紀錄(Stack)
瀏覽器的「返回」與「前進」功能本質上是兩個堆疊。當使用者點擊新頁面時,使用 push 把 URL 放入堆疊;按下「返回」時,用 pop 取出最近的頁面。
let historyStack = [];
// 前往新頁面
function navigate(url) {
historyStack.push(url);
console.log('Current page:', url);
}
// 返回上一頁
function goBack() {
if (historyStack.length > 1) {
historyStack.pop(); // 移除目前頁面
const previous = historyStack[historyStack.length - 1];
console.log('Back to:', previous);
}
}
2. 訊息佇列(Queue)
在即時聊天或事件驅動系統中,訊息會依序進入佇列,然後逐一處理。push 加入訊息,shift 取出最早的訊息。
let messageQueue = [];
// 新訊息進來
function receive(msg) {
messageQueue.push(msg);
}
// 處理佇列
function process() {
while (messageQueue.length) {
const msg = messageQueue.shift();
console.log('Processing:', msg);
}
}
3. 表單動態欄位
在前端表單中,使用者可以點擊「新增欄位」或「刪除欄位」;這時 unshift/push 讓新欄位顯示在最前或最後,shift/pop 則負責移除。
let fields = ['姓名', '電話'];
// 在最前面加一個「電子郵件」欄位
fields.unshift('電子郵件');
// 移除最後一個欄位
fields.pop();
4. 遊戲角色行動順序
回合制遊戲常以陣列保存角色行動順序,使用 push 把新加入的角色排到最後,使用 shift 依序取出當前回合的角色。
let turnOrder = ['玩家A', '玩家B'];
// 新角色加入
turnOrder.push('玩家C');
// 取得本回合角色
function nextTurn() {
return turnOrder.shift(); // 依序返回:玩家A、玩家B、玩家C
}
總結
push/pop操作 陣列尾端,時間複雜度為 O(1),非常適合實作 堆疊 (Stack)。unshift/shift操作 陣列頭部,時間複雜度為 O(n),在大量資料時需留意效能,適合實作 佇列 (Queue) 或需要「頭部」插入的情境。- 了解它們的返回值(新長度或被移除的元素)能避免常見的 副作用 與 錯誤判斷。
- 在函式式編程或需要保持資料不變時,使用展開運算子或
slice複製陣列,以免直接改變原始資料。 - 真實專案中,從 瀏覽器歷史、訊息佇列、動態表單 到 遊戲回合,這四個方法都是不可或缺的基礎工具。
掌握 push、pop、shift、unshift,不只能讓你的程式碼更簡潔、易讀,也能在效能與可維護性之間取得最佳平衡。祝你在 JavaScript 陣列的世界裡玩得開心、寫得順手! 🚀