本文 AI 產出,尚未審核

JavaScript 陣列操作(Arrays)— 類陣列物件(Array‑like Objects)


簡介

在日常的 JavaScript 開發中,我們常會遇到「看起來像陣列」卻不是真正 Array 的資料結構。這類結構被稱為 類陣列物件(Array‑like Objects)。它們具備 length 屬性以及以數字索引存取的特性,讓我們可以用類似陣列的方式遍歷或取值,但卻缺少 Array.prototype 上的大部份方法(如 pushmapfilter 等)。

了解類陣列物件的運作原理,能讓你在處理 argumentsNodeList、字串、以及自訂的類陣列結構 時,寫出更乾淨、效能更佳的程式碼。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握這個主題。


核心概念

什麼是類陣列物件?

類陣列物件具備以下兩個核心特徵:

  1. length 屬性:是一個非負整數,表示「可存取的元素個數」。
  2. 數字索引:屬性名稱為 0、1、2…,可直接透過 obj[0] 方式取得值。

注意:類陣列物件 不繼承 Array.prototype,因此沒有 forEachmap 等方法。

常見的類陣列物件包括:

類型 來源 範例
arguments 函式內部的參數集合 function foo(){ console.log(arguments); }
NodeList DOM 查詢結果 (document.querySelectorAll) document.querySelectorAll('li')
HTMLCollection DOM 元素集合 (document.forms) document.forms
String 字串本身(可視為字元陣列) "hello"
自訂物件 手動模擬 length 與索引 let obj = {0:'a',1:'b', length:2}

為什麼要把類陣列轉成真正的陣列?

雖然可以用 for 迴圈遍歷類陣列,但 陣列方法mapfilterreduce 等)提供了更簡潔且具可讀性的寫法。將類陣列轉換為 Array,可以:

  • 使用鏈式呼叫(chain)提升程式碼可讀性
  • 利用 ES6+ 新語法(如 ...spread)簡化資料處理
  • 避免手動索引錯誤(如 i < obj.length 時忘記 i++

常見的轉換技巧

1. Array.from()

ES6 原生提供的靜態方法,接受類陣列或可迭代物件,回傳新陣列。

// 例:把 arguments 轉成陣列
function sum() {
  const args = Array.from(arguments); // => [1, 2, 3, ...]
  return args.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

優點:語意清晰,可接受第二個映射函式,直接在轉換時完成 map

2. 展開運算子(Spread Operator)

... 會先呼叫物件的 iterable 介面;對於 NodeListarguments 也有效。

const nodeList = document.querySelectorAll('p');
const pArray = [...nodeList]; // 等同於 Array.from(nodeList)

pArray.forEach(p => p.style.color = 'tomato');

注意:若目標不是 iterable(例如自訂的類陣列),此方式會拋出 TypeError

3. Array.prototype.slice.call()

在 ES5 時最常使用的技巧,透過 slice 產生淺拷貝。

function listToArray(list) {
  // list 可能是 arguments、NodeList、HTMLCollection
  return Array.prototype.slice.call(list);
}

// 範例:將 HTMLCollection 轉成陣列
const formsArray = listToArray(document.forms);
formsArray.forEach(form => console.log(form.id));

缺點:語意較不直觀,且在某些環境(如 TypedArray)會失效。

4. 手動迭代(for...of)

若類陣列同時具備 iterable(如 NodeList),可以直接使用 for...of,但仍需要轉成陣列才能使用陣列方法。

const args = [];
for (const arg of arguments) {
  args.push(arg);
}
console.log(args);

程式碼範例

以下提供 4 個實務中常見的類陣列操作範例,說明從「取得」到「轉換」再到「使用」的完整流程。

範例 1️⃣:arguments 轉陣列 + map

function doubleAll() {
  // 把 arguments 轉成陣列
  const nums = Array.from(arguments);
  // 使用 map 產生新陣列
  return nums.map(n => n * 2);
}

console.log(doubleAll(1, 2, 3)); // [2, 4, 6]

說明Array.from 直接把類陣列變成真正的陣列,接著使用 map 完成每個元素的倍增。


範例 2️⃣:NodeList → 展開運算子 → filter

// 取得所有 <li> 元素
const liNodes = document.querySelectorAll('li');

// 把 NodeList 轉成陣列,然後挑出含有 .active 的項目
const activeItems = [...liNodes].filter(li => li.classList.contains('active'));

console.log('Active <li> 數量:', activeItems.length);

說明:展開運算子 ... 可直接把 NodeList 變成陣列,之後使用 filter 取得符合條件的元素。


範例 3️⃣:自訂類陣列物件 → Array.from + 第二參數映射

// 手動建立類陣列物件
const pseudoArray = {
  0: 'apple',
  1: 'banana',
  2: 'cherry',
  length: 3
};

// 轉換時直接把字串轉成大寫
const fruits = Array.from(pseudoArray, item => item.toUpperCase());

console.log(fruits); // ["APPLE", "BANANA", "CHERRY"]

說明Array.from 的第二個參數是 映射函式,讓你在轉換的同時完成資料處理,省去額外的 map 步驟。


範例 4️⃣:HTMLCollectionArray.prototype.slice.callreduce

// 取得所有 <input> 元素(HTMLCollection)
const inputs = document.forms[0].elements;

// 把 HTMLCollection 轉成陣列
const inputArray = Array.prototype.slice.call(inputs);

// 計算所有文字框的總字元長度
const totalLength = inputArray
  .filter(el => el.type === 'text')
  .reduce((sum, el) => sum + el.value.length, 0);

console.log('文字框總字元數:', totalLength);

說明:在舊版瀏覽器(IE)中,Array.from 可能不支援,這時 slice.call 是相容性的備援方案。


常見陷阱與最佳實踐

陷阱 說明 解決方式 / 最佳實踐
忘記 length 不是自動更新 手動改變索引值後,未同步更新 length 會導致遍歷錯誤。 永遠length 為基準,修改索引時同時調整 length,或使用 Array.from 直接產生新陣列。
使用 for...in 迭代 for...in 會遍歷原型鏈上的屬性,可能得到非索引鍵。 只用 forfor...ofArray.fromArray.prototype.forEach(在轉成陣列後)來遍歷。
類陣列不是 iterable 自訂類陣列若缺少 [Symbol.iterator]...spread 會失效。 若需要支援展開運算子,實作 [Symbol.iterator],或直接使用 Array.from
直接改寫 Array.prototype 為類陣列掛上 Array 方法會污染全域,造成不可預期錯誤。 避免 為類陣列直接改寫原型,改以 工具函式(如 toArray)或 ES6 class 包裝。
舊版瀏覽器相容性 Array.from...spread 在 IE 不支援。 使用 polyfill(如 core-js)或 Array.prototype.slice.call 作為後備。

最佳實踐總結

  1. 優先使用 Array.from:語意最清晰,且支援映射函式。
  2. 在需要兼容舊環境時,備用 slice.call
  3. 若類陣列本身是 iterable,直接使用展開運算子,可寫得更簡潔。
  4. 避免直接在類陣列上呼叫 Array.prototype 方法,而是先轉換。
  5. 為自訂類陣列實作 [Symbol.iterator],讓它在所有 ES6+ 場景下都能如陣列般使用。

實際應用場景

場景 為何會碰到類陣列 常用解法
函式參數不定長 (rest 之前) arguments 只能以類陣列形式取得所有參數。 Array.from(arguments)mapfilter 等。
DOM 操作 document.querySelectorAllgetElementsByClassName 回傳 NodeList / HTMLCollection const nodes = [...nodeList];nodes.forEach(...)
表單序列化 form.elementsHTMLCollection,需遍歷取得每個欄位值。 Array.from(form.elements).filter(...).reduce(...)
第三方函式庫 某些舊版函式庫回傳類陣列(如 jQuery 的 $(selector) 內部的 jQuery 物件)。 Array.from($.makeArray(jQueryObj))[].slice.call(jQueryObj)
自訂資料結構 為了效能或記憶體需求,手動建立「類陣列」來儲存大量資料。 為物件實作 [Symbol.iterator] → 直接使用 for...of...spread

總結

類陣列物件是 JavaScript 中 介於純粹物件與正式陣列之間的特殊結構。掌握它們的特性與轉換技巧,能讓你在處理 函式參數、DOM 集合、或自訂資料結構 時,寫出更具可讀性與效能的程式碼。關鍵要點如下:

  • 類陣列 ≠ 陣列:缺少原型方法,必須自行轉換。
  • Array.from 是首選:語意明確,支援映射。
  • 展開運算子 (...) 讓程式更簡潔,但需確保目標是 iterable
  • slice.call 為舊環境的可靠備援。
  • 避免 for...in、直接改寫原型或忘記同步 length,這些是最常見的陷阱。

只要熟練上述技巧,你就能在日常開發中自如地處理各種「看起來像陣列」的資料,提升程式碼品質與維護性。祝你寫程式愉快,持續探索 JavaScript 的無限可能! 🚀