JavaScript 數組方法完全指南

前言

數組是 JavaScript 中最常用的數據結構之一,它提供了豐富的內置方法來處理數據。掌握這些方法不僅能提高開發效率,還能讓代碼更加簡潔優雅。本文將全面介紹 JavaScript 數組的各種方法,幫助你從入門到精通。

目錄

  • 數組基礎
  • 創建數組
  • 數組方法分類
  • 增刪改查方法
  • 遍歷方法
  • 查找方法
  • 轉換方法
  • 排序和反轉
  • 判斷方法
  • 歸約方法
  • ES6+ 新方法
  • 方法對比與選擇
  • 最佳實踐
  • 總結

數組基礎

在 JavaScript 中,數組是一種特殊的對象,用於存儲有序的數據集合。數組具有以下特點:

  • 可調整大小:數組長度可以動態變化
  • 可包含不同類型:同一個數組可以存儲數字、字符串、對象等
  • 索引從 0 開始:第一個元素的索引是 0
  • 淺拷貝:數組複製操作創建的是淺拷貝
// 數組示例
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits[0]); // 'apple'
console.log(fruits.length); // 3

創建數組

1. 字面量方式

const arr1 = []; // 空數組
const arr2 = [1, 2, 3]; // 包含元素的數組
const arr3 = ['a', 'b', 'c', 1, 2, true]; // 混合類型

2. Array 構造函數

const arr1 = new Array(); // 空數組
const arr2 = new Array(5); // 長度為 5 的稀疏數組
const arr3 = new Array(1, 2, 3); // [1, 2, 3]

3. Array.of()

const arr1 = Array.of(7); // [7]
const arr2 = Array.of(1, 2, 3); // [1, 2, 3]
// 與 new Array(7) 不同,Array.of(7) 創建的是包含一個元素 7 的數組

4. Array.from()

// 從類數組對象創建
const arr1 = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
const arr2 = Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]

// 從 Set/Map 創建
const set = new Set([1, 2, 3]);
const arr3 = Array.from(set); // [1, 2, 3]

數組方法分類

增刪改查方法

push() - 在末尾添加元素
const fruits = ['apple', 'banana'];
fruits.push('orange'); // 返回新長度 3
console.log(fruits); // ['apple', 'banana', 'orange']

// 可以一次添加多個元素
fruits.push('grape', 'mango');
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'mango']

特點

  • 修改原數組
  • 返回數組的新長度
  • 可以添加多個元素
pop() - 移除並返回最後一個元素
const fruits = ['apple', 'banana', 'orange'];
const last = fruits.pop(); // 'orange'
console.log(fruits); // ['apple', 'banana']
console.log(last); // 'orange'

特點

  • 修改原數組
  • 返回被移除的元素
  • 如果數組為空,返回 undefined
unshift() - 在開頭添加元素
const fruits = ['banana', 'orange'];
fruits.unshift('apple'); // 返回新長度 3
console.log(fruits); // ['apple', 'banana', 'orange']

特點

  • 修改原數組
  • 返回數組的新長度
  • 性能比 push() 低(需要移動所有元素)
shift() - 移除並返回第一個元素
const fruits = ['apple', 'banana', 'orange'];
const first = fruits.shift(); // 'apple'
console.log(fruits); // ['banana', 'orange']

特點

  • 修改原數組
  • 返回被移除的元素
  • 性能比 pop()
splice() - 刪除、插入或替換元素
const fruits = ['apple', 'banana', 'orange', 'grape'];

// 刪除元素:從索引 1 開始刪除 2 個元素
const removed = fruits.splice(1, 2);
console.log(fruits); // ['apple', 'grape']
console.log(removed); // ['banana', 'orange']

// 插入元素:從索引 1 開始,刪除 0 個,插入新元素
fruits.splice(1, 0, 'mango', 'peach');
console.log(fruits); // ['apple', 'mango', 'peach', 'grape']

// 替換元素:從索引 1 開始,刪除 1 個,插入新元素
fruits.splice(1, 1, 'banana');
console.log(fruits); // ['apple', 'banana', 'peach', 'grape']

語法array.splice(start, deleteCount, ...items)

特點

  • 修改原數組
  • 返回被刪除元素的數組
  • 功能強大,可以刪除、插入、替換
slice() - 提取數組片段(不修改原數組)
const fruits = ['apple', 'banana', 'orange', 'grape', 'mango'];

// 提取從索引 1 到 3(不包括 3)的元素
const sliced = fruits.slice(1, 3);
console.log(sliced); // ['banana', 'orange']
console.log(fruits); // 原數組不變

// 只提供開始索引,提取到末尾
const fromIndex2 = fruits.slice(2);
console.log(fromIndex2); // ['orange', 'grape', 'mango']

// 負數索引:從末尾開始計算
const lastTwo = fruits.slice(-2);
console.log(lastTwo); // ['grape', 'mango']

// 複製整個數組
const copy = fruits.slice();

特點

  • 不修改原數組
  • 返回新數組
  • 常用於數組複製

遍歷方法

forEach() - 遍歷數組
const numbers = [1, 2, 3, 4, 5];

numbers.forEach((value, index, array) => {
  console.log(`索引 ${index}: 值 ${value}`);
});

// 輸出:
// 索引 0: 值 1
// 索引 1: 值 2
// 索引 2: 值 3
// 索引 3: 值 4
// 索引 4: 值 5

// 修改原數組(不推薦,但可以)
const doubled = [];
numbers.forEach(num => {
  doubled.push(num * 2);
});
console.log(doubled); // [2, 4, 6, 8, 10]

特點

  • 不返回新數組(返回 undefined
  • 無法使用 breakcontinue(可以使用 return 跳過當前迭代)
  • 不會跳過空槽(稀疏數組)
map() - 映射數組(返回新數組)
const numbers = [1, 2, 3, 4, 5];

// 將每個數字乘以 2
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // 原數組不變 [1, 2, 3, 4, 5]

// 提取對象屬性
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']

特點

  • 不修改原數組
  • 返回新數組
  • 新數組長度與原數組相同
  • 最常用的數組方法之一
filter() - 過濾數組
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 篩選偶數
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// 篩選對象
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 17 },
  { name: 'Charlie', age: 30 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults); // [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 30 }]

特點

  • 不修改原數組
  • 返回新數組
  • 新數組長度可能小於原數組
  • 回調函數返回 true 的元素會被保留
reduce() - 歸約數組
const numbers = [1, 2, 3, 4, 5];

// 求和
const sum = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0);
console.log(sum); // 15

// 簡化寫法
const sum2 = numbers.reduce((acc, cur) => acc + cur, 0);

// 求最大值
const max = numbers.reduce((acc, cur) => Math.max(acc, cur));

// 數組轉對象
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];
const userMap = users.reduce((acc, user) => {
  acc[user.id] = user.name;
  return acc;
}, {});
console.log(userMap); // { 1: 'Alice', 2: 'Bob', 3: 'Charlie' }

// 數組扁平化
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, cur) => acc.concat(cur), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]

特點

  • 不修改原數組
  • 返回單個值
  • 功能強大,可以實現很多複雜操作
  • 初始值可選(建議總是提供)
reduceRight() - 從右到左歸約
const numbers = [1, 2, 3, 4];

// 從右到左計算
const result = numbers.reduceRight((acc, cur) => acc - cur);
console.log(result); // -2 (4 - 3 - 2 - 1 = -2)

// 與 reduce 對比
const result2 = numbers.reduce((acc, cur) => acc - cur);
console.log(result2); // -8 (1 - 2 - 3 - 4 = -8)

查找方法

indexOf() - 查找元素索引
const fruits = ['apple', 'banana', 'orange', 'banana'];

const index = fruits.indexOf('banana');
console.log(index); // 1

// 從指定位置開始查找
const index2 = fruits.indexOf('banana', 2);
console.log(index2); // 3

// 找不到返回 -1
const index3 = fruits.indexOf('grape');
console.log(index3); // -1

特點

  • 使用嚴格相等(===)比較
  • 返回第一個匹配的索引
  • 找不到返回 -1
lastIndexOf() - 從後往前查找
const fruits = ['apple', 'banana', 'orange', 'banana'];

const index = fruits.lastIndexOf('banana');
console.log(index); // 3
includes() - 判斷是否包含元素
const fruits = ['apple', 'banana', 'orange'];

console.log(fruits.includes('banana')); // true
console.log(fruits.includes('grape')); // false

// 從指定位置開始查找
console.log(fruits.includes('apple', 1)); // false

特點

  • 返回布爾值
  • ES6 新增方法
  • indexOf() !== -1 更語義化
find() - 查找第一個滿足條件的元素
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 17 },
  { id: 3, name: 'Charlie', age: 30 }
];

// 查找第一個年齡大於等於 18 的用户
const adult = users.find(user => user.age >= 18);
console.log(adult); // { id: 1, name: 'Alice', age: 25 }

// 找不到返回 undefined
const senior = users.find(user => user.age > 100);
console.log(senior); // undefined

特點

  • 不修改原數組
  • 返回第一個匹配的元素
  • 找不到返回 undefined
findIndex() - 查找第一個滿足條件的元素索引
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 17 },
  { id: 3, name: 'Charlie', age: 30 }
];

const index = users.findIndex(user => user.age >= 18);
console.log(index); // 0

// 找不到返回 -1
const notFound = users.findIndex(user => user.age > 100);
console.log(notFound); // -1
findLast() - 查找最後一個滿足條件的元素(ES2023)
const numbers = [1, 2, 3, 4, 5, 6];

const lastEven = numbers.findLast(num => num % 2 === 0);
console.log(lastEven); // 6
findLastIndex() - 查找最後一個滿足條件的元素索引(ES2023)
const numbers = [1, 2, 3, 4, 5, 6];

const lastEvenIndex = numbers.findLastIndex(num => num % 2 === 0);
console.log(lastEvenIndex); // 5

轉換方法

join() - 數組轉字符串
const fruits = ['apple', 'banana', 'orange'];

console.log(fruits.join()); // 'apple,banana,orange'
console.log(fruits.join('')); // 'applebananaorange'
console.log(fruits.join(' - ')); // 'apple - banana - orange'

特點

  • 不修改原數組
  • 默認使用逗號分隔
  • 空數組返回空字符串
toString() - 轉換為字符串
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.toString()); // 'apple,banana,orange'
// 等同於 fruits.join()
toLocaleString() - 本地化字符串
const numbers = [1234.56, 7890.12];
console.log(numbers.toLocaleString('zh-CN')); // '1,234.56,7,890.12'
flat() - 扁平化數組
const nested = [1, [2, 3], [4, [5, 6]]];

// 默認只扁平化一層
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]

// 指定深度
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]

// 扁平化所有層級
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

特點

  • 不修改原數組
  • ES2019 新增
  • 可以指定扁平化深度
flatMap() - 映射後扁平化
const numbers = [1, 2, 3];

// 相當於 map().flat()
const result = numbers.flatMap(x => [x, x * 2]);
console.log(result); // [1, 2, 2, 4, 3, 6]

// 與 map().flat() 對比
const result2 = numbers.map(x => [x, x * 2]).flat();
console.log(result2); // [1, 2, 2, 4, 3, 6]

特點

  • 不修改原數組
  • ES2019 新增
  • 性能比 map().flat() 更好

排序和反轉

sort() - 排序
const numbers = [3, 1, 4, 1, 5, 9, 2, 6];

// 默認按字符串排序(不推薦)
numbers.sort();
console.log(numbers); // [1, 1, 2, 3, 4, 5, 6, 9]

// 數字排序(升序)
const numbers2 = [3, 1, 4, 1, 5, 9, 2, 6];
numbers2.sort((a, b) => a - b);
console.log(numbers2); // [1, 1, 2, 3, 4, 5, 6, 9]

// 數字排序(降序)
const numbers3 = [3, 1, 4, 1, 5, 9, 2, 6];
numbers3.sort((a, b) => b - a);
console.log(numbers3); // [9, 6, 5, 4, 3, 2, 1, 1]

// 對象排序
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 17 },
  { name: 'Charlie', age: 30 }
];
users.sort((a, b) => a.age - b.age);
console.log(users);
// [{ name: 'Bob', age: 17 }, { name: 'Alice', age: 25 }, { name: 'Charlie', age: 30 }]

特點

  • 修改原數組
  • 默認按字符串 Unicode 碼點排序
  • 需要提供比較函數進行數字排序
reverse() - 反轉數組
const fruits = ['apple', 'banana', 'orange'];
fruits.reverse();
console.log(fruits); // ['orange', 'banana', 'apple']

特點

  • 修改原數組
  • 返回反轉後的數組(原數組的引用)
toSorted() - 排序(不修改原數組,ES2023)
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(numbers); // [3, 1, 4, 1, 5] 原數組不變
toReversed() - 反轉(不修改原數組,ES2023)
const fruits = ['apple', 'banana', 'orange'];
const reversed = fruits.toReversed();
console.log(reversed); // ['orange', 'banana', 'apple']
console.log(fruits); // ['apple', 'banana', 'orange'] 原數組不變

判斷方法

every() - 判斷是否所有元素都滿足條件
const numbers = [2, 4, 6, 8, 10];

// 判斷是否都是偶數
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // true

const numbers2 = [2, 4, 6, 7, 8];
const allEven2 = numbers2.every(num => num % 2 === 0);
console.log(allEven2); // false

特點

  • 不修改原數組
  • 返回布爾值
  • 空數組返回 true(空真值)
some() - 判斷是否有元素滿足條件
const numbers = [1, 3, 5, 7, 8];

// 判斷是否有偶數
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true

const numbers2 = [1, 3, 5, 7, 9];
const hasEven2 = numbers2.some(num => num % 2 === 0);
console.log(hasEven2); // false

特點

  • 不修改原數組
  • 返回布爾值
  • 空數組返回 false

歸約方法

reduce() 和 reduceRight()

已在前面詳細介紹,這裏不再重複。


ES6+ 新方法

Array.from() - 從類數組創建數組
// 從字符串
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']

// 從 Set
Array.from(new Set([1, 2, 3])); // [1, 2, 3]

// 從對象(需要 length 屬性)
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]

// 從 NodeList
const divs = document.querySelectorAll('div');
const divArray = Array.from(divs);
Array.of() - 創建數組
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
// 與 new Array(7) 不同
fill() - 填充數組
// 填充整個數組
const arr1 = new Array(5).fill(0);
console.log(arr1); // [0, 0, 0, 0, 0]

// 從指定位置填充
const arr2 = [1, 2, 3, 4, 5];
arr2.fill(0, 2, 4);
console.log(arr2); // [1, 2, 0, 0, 5]

特點

  • 修改原數組
  • 可以指定填充的起始和結束位置
copyWithin() - 複製數組元素
const arr = [1, 2, 3, 4, 5];

// 將索引 0-2 的元素複製到索引 3 開始的位置
arr.copyWithin(3, 0, 3);
console.log(arr); // [1, 2, 3, 1, 2]

特點

  • 修改原數組
  • 使用場景較少
entries() - 返回鍵值對迭代器
const fruits = ['apple', 'banana', 'orange'];

for (const [index, value] of fruits.entries()) {
  console.log(index, value);
}
// 0 'apple'
// 1 'banana'
// 2 'orange'
keys() - 返回鍵迭代器
const fruits = ['apple', 'banana', 'orange'];

for (const index of fruits.keys()) {
  console.log(index);
}
// 0
// 1
// 2
values() - 返回值迭代器
const fruits = ['apple', 'banana', 'orange'];

for (const value of fruits.values()) {
  console.log(value);
}
// 'apple'
// 'banana'
// 'orange'
at() - 按索引訪問(支持負數,ES2022)
const fruits = ['apple', 'banana', 'orange'];

console.log(fruits.at(0)); // 'apple'
console.log(fruits.at(-1)); // 'orange'(最後一個)
console.log(fruits.at(-2)); // 'banana'(倒數第二個)

特點

  • 不修改原數組
  • 支持負數索引
  • fruits[fruits.length - 1] 更優雅
with() - 修改指定索引的值(不修改原數組,ES2023)
const fruits = ['apple', 'banana', 'orange'];
const newFruits = fruits.with(1, 'grape');
console.log(newFruits); // ['apple', 'grape', 'orange']
console.log(fruits); // ['apple', 'banana', 'orange'] 原數組不變
toSpliced() - 刪除、插入或替換(不修改原數組,ES2023)
const fruits = ['apple', 'banana', 'orange'];
const newFruits = fruits.toSpliced(1, 1, 'grape', 'mango');
console.log(newFruits); // ['apple', 'grape', 'mango', 'orange']
console.log(fruits); // ['apple', 'banana', 'orange'] 原數組不變

方法對比與選擇

修改原數組 vs 不修改原數組

修改原數組

不修改原數組

push, pop, shift, unshift

map, filter, slice

splice, sort, reverse

concat, join, toString

fill, copyWithin

flat, flatMap

toSorted, toReversed, with, toSpliced

選擇建議

  • 優先使用不修改原數組的方法(函數式編程)
  • 需要修改原數組時,明確知道副作用

查找方法對比

方法

返回值

使用場景

indexOf

索引或 -1

簡單值查找

includes

布爾值

判斷是否存在

find

元素或 undefined

對象查找

findIndex

索引或 -1

對象查找索引

some

布爾值

判斷是否有滿足條件的元素

遍歷方法對比

方法

返回值

是否可中斷

使用場景

forEach

undefined


簡單遍歷

map

新數組


轉換數組

filter

新數組


過濾數組

for…of

-


需要中斷的遍歷

reduce

單個值


歸約計算


最佳實踐

1. 鏈式調用

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 鏈式調用多個方法
const result = numbers
  .filter(num => num % 2 === 0)  // [2, 4, 6, 8, 10]
  .map(num => num * 2)            // [4, 8, 12, 16, 20]
  .reduce((sum, num) => sum + num, 0); // 60

console.log(result); // 60

2. 避免在遍歷中修改數組

// ❌ 不推薦:在 forEach 中修改數組長度
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num, index) => {
  if (num % 2 === 0) {
    numbers.splice(index, 1); // 危險!會跳過元素
  }
});

// ✅ 推薦:使用 filter
const numbers2 = [1, 2, 3, 4, 5];
const odds = numbers2.filter(num => num % 2 !== 0);

3. 使用解構賦值

// 交換變量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1

// 獲取第一個和剩餘元素
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]

4. 性能優化

// ❌ 不推薦:多次遍歷
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
const doubled = evens.map(n => n * 2);
const sum = doubled.reduce((a, b) => a + b, 0);

// ✅ 推薦:單次遍歷
const sum2 = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((a, b) => a + b, 0);

// ✅ 更推薦:使用 reduce 一次完成
const sum3 = numbers.reduce((acc, n) => {
  if (n % 2 === 0) {
    return acc + n * 2;
  }
  return acc;
}, 0);

5. 處理空值和邊界情況

// 安全的數組操作
function safeArrayOperation(arr) {
  if (!Array.isArray(arr) || arr.length === 0) {
    return [];
  }
  return arr.map(item => item * 2);
}

// 使用可選鏈和空值合併
const result = array?.map(x => x * 2) ?? [];

6. 數組去重

// 方法 1:使用 Set
const unique1 = [...new Set([1, 2, 2, 3, 3, 3])];
console.log(unique1); // [1, 2, 3]

// 方法 2:使用 filter + indexOf
const unique2 = [1, 2, 2, 3, 3, 3].filter((item, index, arr) => 
  arr.indexOf(item) === index
);

// 方法 3:對象數組去重
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' }
];
const uniqueUsers = users.filter((user, index, arr) => 
  arr.findIndex(u => u.id === user.id) === index
);
console.log(uniqueUsers); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

// 方法 4:使用 Map(性能更好)
const uniqueUsers2 = Array.from(
  new Map(users.map(user => [user.id, user])).values()
);

7. 數組分組

// 使用 reduce 實現分組
const users = [
  { name: 'Alice', age: 25, city: 'Beijing' },
  { name: 'Bob', age: 30, city: 'Shanghai' },
  { name: 'Charlie', age: 25, city: 'Beijing' },
  { name: 'David', age: 30, city: 'Shanghai' }
];

// 按城市分組
const groupedByCity = users.reduce((acc, user) => {
  const city = user.city;
  if (!acc[city]) {
    acc[city] = [];
  }
  acc[city].push(user);
  return acc;
}, {});
console.log(groupedByCity);
// {
//   Beijing: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
//   Shanghai: [{ name: 'Bob', ... }, { name: 'David', ... }]
// }

// 按年齡分組
const groupedByAge = users.reduce((acc, user) => {
  const age = user.age;
  acc[age] = acc[age] || [];
  acc[age].push(user);
  return acc;
}, {});

8. 數組分塊

// 將數組分割成指定大小的塊
function chunk(array, size) {
  const chunks = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(chunk(numbers, 3)); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

// 使用 reduce 實現
const chunk2 = (array, size) => 
  array.reduce((acc, _, i) => {
    if (i % size === 0) {
      acc.push(array.slice(i, i + size));
    }
    return acc;
  }, []);

9. 數組合並

// concat() - 合併數組(不修改原數組)
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2, 3] 原數組不變

// 使用擴展運算符(推薦)
const arr4 = [...arr1, ...arr2];
console.log(arr4); // [1, 2, 3, 4, 5, 6]

// 合併多個數組
const arr5 = [7, 8, 9];
const merged = [...arr1, ...arr2, ...arr5];
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

10. 數組交集、並集、差集

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [4, 5, 6, 7, 8];

// 交集:兩個數組都有的元素
const intersection = arr1.filter(item => arr2.includes(item));
console.log(intersection); // [4, 5]

// 並集:兩個數組的所有元素(去重)
const union = [...new Set([...arr1, ...arr2])];
console.log(union); // [1, 2, 3, 4, 5, 6, 7, 8]

// 差集:arr1 有但 arr2 沒有的元素
const difference = arr1.filter(item => !arr2.includes(item));
console.log(difference); // [1, 2, 3]

// 對稱差集:兩個數組獨有的元素
const symmetricDifference = [
  ...arr1.filter(item => !arr2.includes(item)),
  ...arr2.filter(item => !arr1.includes(item))
];
console.log(symmetricDifference); // [1, 2, 3, 6, 7, 8]

11. 數組隨機打亂

// Fisher-Yates 洗牌算法
function shuffle(array) {
  const arr = [...array]; // 避免修改原數組
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

const numbers = [1, 2, 3, 4, 5];
console.log(shuffle(numbers)); // 隨機順序
console.log(numbers); // [1, 2, 3, 4, 5] 原數組不變

12. 數組扁平化(多種方法)

const nested = [1, [2, 3], [4, [5, 6]]];

// 方法 1:使用 flat()
const flat1 = nested.flat(Infinity);

// 方法 2:使用 reduce + concat
const flat2 = nested.reduce((acc, cur) => 
  acc.concat(Array.isArray(cur) ? flat2(cur) : cur), []
);

// 方法 3:使用擴展運算符(僅一層)
const flat3 = [].concat(...nested);

// 方法 4:使用 toString()(僅適用於數字)
const numbers = [[1, 2], [3, 4]];
const flat4 = numbers.toString().split(',').map(Number);

13. 數組統計

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 求和
const sum = numbers.reduce((a, b) => a + b, 0);

// 平均值
const average = numbers.reduce((a, b) => a + b, 0) / numbers.length;

// 最大值和最小值
const max = Math.max(...numbers);
const min = Math.min(...numbers);

// 使用 reduce 求最大值和最小值
const max2 = numbers.reduce((a, b) => Math.max(a, b));
const min2 = numbers.reduce((a, b) => Math.min(a, b));

// 統計元素出現次數
const count = numbers.reduce((acc, num) => {
  acc[num] = (acc[num] || 0) + 1;
  return acc;
}, {});

14. 數組轉對象

// 鍵值對數組轉對象
const entries = [['name', 'Alice'], ['age', 25], ['city', 'Beijing']];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'Alice', age: 25, city: 'Beijing' }

// 對象數組轉對象(以某個屬性為鍵)
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];
const userMap = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});
console.log(userMap);
// {
//   1: { id: 1, name: 'Alice' },
//   2: { id: 2, name: 'Bob' },
//   3: { id: 3, name: 'Charlie' }
// }

15. 數組補全方法補充

concat() - 合併數組
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合併數組
const merged = arr1.concat(arr2);
console.log(merged); // [1, 2, 3, 4, 5, 6]

// 可以合併多個數組
const arr3 = [7, 8, 9];
const all = arr1.concat(arr2, arr3);
console.log(all); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 也可以合併值
const withValues = arr1.concat(4, 5, [6, 7]);
console.log(withValues); // [1, 2, 3, 4, 5, 6, 7]

特點

  • 不修改原數組
  • 返回新數組
  • 可以合併多個數組或值

常見應用場景

場景 1:數據處理和轉換

// 從 API 獲取數據後處理
const apiData = [
  { id: 1, name: 'Alice', status: 'active' },
  { id: 2, name: 'Bob', status: 'inactive' },
  { id: 3, name: 'Charlie', status: 'active' }
];

// 提取活躍用户的名字
const activeUsers = apiData
  .filter(user => user.status === 'active')
  .map(user => user.name);
console.log(activeUsers); // ['Alice', 'Charlie']

場景 2:表單驗證

// 驗證表單字段
const formFields = [
  { name: 'username', value: 'alice', required: true },
  { name: 'email', value: '', required: true },
  { name: 'age', value: '25', required: false }
];

// 檢查必填字段是否都已填寫
const isValid = formFields
  .filter(field => field.required)
  .every(field => field.value.trim() !== '');

console.log(isValid); // false(email 為空)

場景 3:購物車計算

const cart = [
  { id: 1, name: '商品A', price: 100, quantity: 2 },
  { id: 2, name: '商品B', price: 200, quantity: 1 },
  { id: 3, name: '商品C', price: 50, quantity: 3 }
];

// 計算總價
const total = cart.reduce((sum, item) => 
  sum + item.price * item.quantity, 0
);
console.log(total); // 450

// 計算商品總數
const totalQuantity = cart.reduce((sum, item) => 
  sum + item.quantity, 0
);
console.log(totalQuantity); // 6

場景 4:數據篩選和搜索

const products = [
  { id: 1, name: 'iPhone', category: '手機', price: 5000 },
  { id: 2, name: 'iPad', category: '平板', price: 3000 },
  { id: 3, name: 'MacBook', category: '電腦', price: 10000 }
];

// 按價格篩選
const affordable = products.filter(p => p.price < 5000);
console.log(affordable); // [{ id: 2, name: 'iPad', ... }]

// 搜索功能
function searchProducts(products, keyword) {
  return products.filter(product => 
    product.name.toLowerCase().includes(keyword.toLowerCase())
  );
}

console.log(searchProducts(products, 'phone')); 
// [{ id: 1, name: 'iPhone', ... }]

場景 5:數據分組和聚合

const orders = [
  { date: '2024-01-01', amount: 100, category: '電子產品' },
  { date: '2024-01-01', amount: 50, category: '食品' },
  { date: '2024-01-02', amount: 200, category: '電子產品' },
  { date: '2024-01-02', amount: 30, category: '食品' }
];

// 按日期分組並計算總金額
const dailyTotal = orders.reduce((acc, order) => {
  const date = order.date;
  acc[date] = (acc[date] || 0) + order.amount;
  return acc;
}, {});
console.log(dailyTotal);
// { '2024-01-01': 150, '2024-01-02': 230 }

// 按類別分組
const categoryTotal = orders.reduce((acc, order) => {
  const category = order.category;
  acc[category] = (acc[category] || 0) + order.amount;
  return acc;
}, {});
console.log(categoryTotal);
// { '電子產品': 300, '食品': 80 }

性能注意事項

1. 方法選擇

// ❌ 性能較差:多次遍歷
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
const doubled = evens.map(n => n * 2);

// ✅ 性能較好:單次遍歷
const doubled2 = numbers.reduce((acc, n) => {
  if (n % 2 === 0) {
    acc.push(n * 2);
  }
  return acc;
}, []);

2. 大數組處理

// 對於大數組,考慮使用 for 循環
const largeArray = new Array(1000000).fill(0).map((_, i) => i);

// ❌ 可能較慢
const result1 = largeArray.map(x => x * 2);

// ✅ 可能更快
const result2 = [];
for (let i = 0; i < largeArray.length; i++) {
  result2[i] = largeArray[i] * 2;
}

3. 提前退出

// 使用 some() 或 find() 可以提前退出
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ✅ 找到第一個偶數就停止
const firstEven = numbers.find(n => n % 2 === 0);

// ❌ 會遍歷所有元素
const firstEven2 = numbers.filter(n => n % 2 === 0)[0];

總結

JavaScript 數組提供了豐富而強大的方法,掌握這些方法可以大大提高開發效率。以下是關鍵要點:

核心方法分類

  1. 增刪改查push, pop, shift, unshift, splice, slice
  2. 遍歷轉換forEach, map, filter, reduce
  3. 查找判斷find, findIndex, includes, some, every
  4. 排序反轉sort, reverse, toSorted, toReversed
  5. 轉換方法join, flat, flatMap, concat

選擇原則

  • 優先使用不修改原數組的方法(函數式編程)
  • 根據需求選擇合適的方法(查找用 find,判斷用 some/every
  • 鏈式調用提高代碼可讀性
  • 注意性能,大數組考慮使用傳統循環

最佳實踐

  1. ✅ 使用 map 轉換數組
  2. ✅ 使用 filter 過濾數組
  3. ✅ 使用 reduce 進行復雜計算
  4. ✅ 使用 find 查找元素
  5. ✅ 使用 includes 判斷存在性
  6. ❌ 避免在遍歷中修改數組
  7. ❌ 避免不必要的多次遍歷

學習建議

  1. 理解每個方法的返回值:是否修改原數組、返回什麼類型
  2. 掌握鏈式調用:組合使用多個方法
  3. 熟悉常見場景:去重、分組、統計等
  4. 注意邊界情況:空數組、undefined、null 等
  5. 關注性能:大數組時選擇合適的方法

希望這篇指南能幫助你更好地掌握 JavaScript 數組方法,在實際開發中靈活運用!