本文 AI 產出,尚未審核

JavaScript 陣列操作(Arrays)── push / pop / shift / unshift

簡介

在 JavaScript 中,陣列 (Array) 是最常用的資料結構之一。它不僅能儲存同類型或不同類型的資料,還提供了一組直覺且高效的 API,讓我們可以輕鬆地在陣列的兩端加入或移除元素。
本單元聚焦於四個最基礎、最常被使用的操作:pushpopshiftunshift。掌握它們不只讓程式碼更簡潔,也能避免許多因手動管理索引而產生的錯誤。

為什麼要特別學習這四個方法?

  1. 可讀性:使用語意化的 API(例如 push 代表「推入」),比手動調整 length 或使用 array[array.length] = x 更易懂。
  2. 效能:在大多數瀏覽器實作中,這四個方法已經過最佳化,尤其在 O(1) 時間複雜度下完成操作。
  3. 實務需求:從資料佇列、堆疊到 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 會產生較大的記憶體搬移成本,請斟酌是否真的需要「頭部」插入。


常見陷阱與最佳實踐

陷阱 可能的結果 解決方式或最佳實踐
忘記 pushunshift 會回傳新長度 直接把回傳值當作陣列使用,導致錯誤 只在需要長度時才使用回傳值,否則直接操作原陣列
在迴圈內同時使用 pushpop 可能不小心改變迭代次數,導致漏掉元素 使用 while (arr.length) 或先複製陣列再迭代
誤以為 shift/unshift 為 O(1) 大型陣列會出現性能瓶頸 若需求是高效佇列,考慮 Array.prototype.sliceLinkedList 或第三方庫(如 deque
直接改變參數傳入的陣列 造成副作用,特別在函式式編程中不易追蹤 使用 const newArr = [...oldArr, newItem](等價於 push)或 oldArr.slice() 複製
使用 pop/shift 取得值後忘記檢查 undefined 空陣列時會得到 undefined,但程式可能繼續執行 在使用前先檢查 if (arr.length) { ... }

小技巧與最佳寫法

  1. 一次加入多個元素
    pushunshift 都支援傳入多個參數,省去多次呼叫的開銷。

    // 同時加入三筆資料
    stack.push(1, 2, 3);
    
  2. 結合展開運算子建立新陣列(避免副作用)

    const original = [1, 2];
    const added = [...original, 3];   // 等同於 original.push(3) 但不改變 original
    
  3. 使用 Array.fromArray.prototype.slice 複製

    const copy = original.slice();    // 或 Array.from(original)
    
  4. 在需要「先入先出」的佇列時,使用 push + shift

    const 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 複製陣列,以免直接改變原始資料。
  • 真實專案中,從 瀏覽器歷史訊息佇列動態表單遊戲回合,這四個方法都是不可或缺的基礎工具。

掌握 pushpopshiftunshift,不只能讓你的程式碼更簡潔、易讀,也能在效能與可維護性之間取得最佳平衡。祝你在 JavaScript 陣列的世界裡玩得開心、寫得順手! 🚀