在前端開發中,數組扁平化(Flattening an Array)是一個常見的操作,特別是在處理嵌套數組時。數組扁平化的過程就是將多維數組轉化為一維數組。這在許多場景下都非常有用,例如處理API返回的數據、操作複雜的列表、或是優化數據結構。

本文將深入探討如何在 JavaScript 中實現數組扁平化,並展示幾種常見的扁平化方法,幫助你更好地處理嵌套數組。

目錄

  1. 數組扁平化的定義與應用
  2. 數組扁平化的常見方法
  • 使用 flat() 方法
  • 使用遞歸
  • 使用 reduce() 方法
  • 使用棧(Stack)
  1. 處理不同深度的嵌套數組
  2. 數組扁平化的性能優化
  3. 實踐示例
  4. 小結

1. 數組扁平化的定義與應用

數組扁平化是指將嵌套的數組轉換為一維數組。嵌套的數組可能有不同的深度,即數組內部可能包含多個數組或其他複雜數據結構。

例如,對於如下的嵌套數組:

const nestedArray = [1, [2, [3, [4, 5]]]];

通過扁平化後,我們將其轉化為一維數組:

const flatArray = [1, 2, 3, 4, 5];

在實際開發中,我們常常遇到需要將複雜的多維數組轉為一維數組的場景,例如處理從API中返回的數據,或者在多層級的對象中提取信息等。

2. 數組扁平化的常見方法

2.1 使用 flat() 方法

從 ECMAScript 2019 (ES10) 開始,JavaScript 提供了一個內建的 flat() 方法來處理數組的扁平化。該方法可以將數組中的嵌套數組“拉平”,默認情況下,它只會扁平化一層。

const nestedArray = [1, [2, [3, [4, 5]]]];
const flatArray = nestedArray.flat();
console.log(flatArray); // [1, 2, [3, [4, 5]]]

如果想要完全扁平化數組,可以傳遞一個參數來指定需要“拉平”的深度。默認值為 1,如果傳入 Infinity,則可以將數組完全扁平化。

const deeplyNestedArray = [1, [2, [3, [4, 5]]]];
const completelyFlatArray = deeplyNestedArray.flat(Infinity);
console.log(completelyFlatArray); // [1, 2, 3, 4, 5]

2.2 使用遞歸

遞歸是一種常見的處理嵌套結構的方法。在進行數組扁平化時,我們可以通過遞歸來逐層拆解數組,並將所有的元素合併到一個新的數組中。

function flattenArray(arr) {
  let result = [];
  
  arr.forEach(item => {
    if (Array.isArray(item)) {
      result = result.concat(flattenArray(item)); // 如果是數組,則遞歸調用
    } else {
      result.push(item); // 如果是基本類型,則直接添加到結果數組
    }
  });
  
  return result;
}

const nestedArray = [1, [2, [3, [4, 5]]]];
console.log(flattenArray(nestedArray)); // [1, 2, 3, 4, 5]

這種方法非常靈活,不受深度限制,並且可以處理任意深度的嵌套數組。

2.3 使用 reduce() 方法

reduce() 是一個非常強大的數組方法,通常用於彙總數據。在數組扁平化時,reduce() 可以通過不斷累加數組中的元素來實現。

function flattenArray(arr) {
  return arr.reduce((acc, item) => {
    if (Array.isArray(item)) {
      acc = acc.concat(flattenArray(item)); // 遞歸扁平化
    } else {
      acc.push(item); // 添加非數組元素
    }
    return acc;
  }, []);
}

const nestedArray = [1, [2, [3, [4, 5]]]];
console.log(flattenArray(nestedArray)); // [1, 2, 3, 4, 5]

reduce() 方法通過一個回調函數累計數組元素,遞歸地處理嵌套數組,最終得到一個一維數組。

2.4 使用棧(Stack)

棧是一種非常有用的數據結構,利用棧的先進後出(LIFO)特性,我們可以實現數組的扁平化。棧的方法將數組逐一壓入棧中,然後逐一彈出,直到沒有更多的元素。

function flattenArray(arr) {
  let stack = [...arr];
  let result = [];
  
  while (stack.length) {
    let item = stack.pop();
    if (Array.isArray(item)) {
      stack.push(...item); // 如果是數組,壓入棧中
    } else {
      result.unshift(item); // 如果是基本類型,添加到結果數組
    }
  }
  
  return result;
}

const nestedArray = [1, [2, [3, [4, 5]]]];
console.log(flattenArray(nestedArray)); // [1, 2, 3, 4, 5]

這種方法的好處是可以避免遞歸帶來的函數調用棧溢出問題,特別是在處理非常大的嵌套數組時。

3. 處理不同深度的嵌套數組

在實際應用中,我們可能會遇到數組深度不確定的情況。上述方法(如 flat() 方法和遞歸方法)可以輕鬆處理任意深度的嵌套數組。但如果數組深度非常大,可能會遇到性能問題。

3.1 使用 flat() 和 Infinity

如果我們需要扁平化的數組層級深度未知或者不固定,使用 flat() 方法並指定 Infinity 參數是一種簡單且有效的解決方案。

const nestedArray = [1, [2, [3, [4, [5]]]]];
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5]

3.2 避免遞歸的棧溢出

如果數組嵌套深度過大,遞歸方法可能導致棧溢出。此時,可以考慮使用棧的方式來扁平化數組,避免遞歸調用。

4. 數組扁平化的性能優化

對於大量數據或深度較大的數組,數組扁平化的性能可能成為一個瓶頸。這裏有幾個優化方法:

  1. 避免深度遞歸:遞歸深度過大會導致棧溢出問題。可以考慮使用 flat() 或棧的方式來避免遞歸。
  2. 合併數組時使用 push():儘量避免使用 concat() 多次合併數組,concat() 會創建新數組並拷貝所有元素,可能導致性能問題。可以使用 push() 或 unshift() 來直接操作數組。
  3. 選擇合適的扁平化深度:當我們明確知道數組的深度時,最好指定適當的深度,而不是使用 Infinity

5. 實踐示例

假設你在開發一個電商網站,返回的商品數據包含多層嵌套數組,你需要將其扁平化後顯示。

const products = [
  { id: 1, name: 'Laptop', variants: [{ color: 'Black' }, { color: 'White' }] },
  { id: 2, name: 'Phone', variants: [{ color: 'Blue' }, { color: 'Red' }] },
  { id: 3, name: 'Tablet', variants: [{ color: 'Silver' }] }
];

// 扁平化所有的顏色數據
const flattenVariants = products.map(product => product.variants).flat();
const flatColors = flattenVariants.map(variant => variant.color);
console.log(flatColors); // ['Black', 'White', 'Blue', 'Red', 'Silver']

在這個例子中,使用了 map()flat() 方法將嵌套數組扁平化為一維數組,方便後續處理。

6. 小結

數組扁平化是處理嵌套數組時的常見需求,JavaScript 提供了多種方法來實現這一目標,從內建的 flat() 方法到遞歸和棧的方式,每種方法都有其優缺點。根據數據的複雜度和嵌套層數,可以選擇適合的方法來扁平化數組