JavaScript 数组

数组和对象并称 JS 两大核心数据结构。数组是有序、可索引的列表,用于存放一组数据:用户列表、商品列表、购物车……几乎每个网页都要处理数组。这一章我们系统掌握数组,重点学 mapfilterreduce 这三大"函数式"方法。

1. 数组基础

// 数组:有序的值列表,用 [] 创建
const fruits = ["apple", "banana", "cherry"];

// 访问元素(下标从 0 开始)
console.log(fruits[0]);        // apple
console.log(fruits.length);    // 3
console.log(fruits[fruits.length - 1]);  // cherry(最后一个)

// 修改元素
fruits[1] = "blueberry";
console.log(fruits);           // ["apple","blueberry","cherry"]

// JS 数组可以放任意类型(但通常同质)
const mixed = [1, "hi", true, null, { name: "小明" }];

// 检查是否是数组(不要用 typeof,会返回 "object")
console.log(Array.isArray(fruits));   // true
console.log(Array.isArray("hi"));     // false

JS 的数组可以放任意类型(不像 Java/C 数组必须同质),长度可变(不像静态语言需要预先分配)。判断是否数组要用 Array.isArray(),typeof [] 返回的是 "object"——这是经典坑。

2. 增删改查

// 末尾增删
const arr = [1, 2, 3];
arr.push(4);              // 末尾添加,返回新长度
console.log(arr);         // [1,2,3,4]
arr.pop();                // 末尾删除,返回被删的值
console.log(arr);         // [1,2,3]

// 开头增删
arr.unshift(0);           // 开头添加
console.log(arr);         // [0,1,2,3]
arr.shift();              // 开头删除
console.log(arr);         // [1,2,3]

// 任意位置增删:splice(起始下标, 删除数量, ...要插入的元素)
const nums = [10, 20, 30, 40];
nums.splice(1, 1);        // 从下标 1 删 1 个
console.log(nums);        // [10,30,40]
nums.splice(1, 0, 99);    // 从下标 1 删 0 个,插入 99
console.log(nums);        // [10,99,30,40]
nums.splice(2, 1, "a", "b"); // 替换:删 1 个,加 2 个
console.log(nums);        // [10,99,"a","b",40]

记忆口诀:

shiftunshift 性能比 push/pop 差,因为要移动所有元素,大数组上慎用。

3. 遍历数组

// 1. for-of:遍历值
const arr = [10, 20, 30];
for (const item of arr) {
  console.log(item);     // 10 / 20 / 30
}

// 2. 经典 for:需要索引
for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);
}

// 3. forEach:简单遍历(无返回值,不能 break)
arr.forEach((item, index) => {
  console.log(index, item);
});

// 4. map:对每个元素做处理,返回新数组
const doubled = arr.map(n => n * 2);
console.log(doubled);    // [20,40,60]

// 5. filter:筛选满足条件的元素
const big = arr.filter(n => n > 15);
console.log(big);        // [20,30]

// 6. find:找第一个满足条件的元素
const first = arr.find(n => n > 15);
console.log(first);      // 20

// 7. some / every:判断
console.log(arr.some(n => n > 25));   // true(有任一满足)
console.log(arr.every(n => n > 0));   // true(全部满足)

实战中 map/filter/forEach 用得最多——它们让代码更声明式、更简洁。能用这些方法的,就别写手写 for 循环。

4. reduce:汇总神器

reduce 是数组方法里最强、也最容易把新手搞晕的。它的作用是把数组"压缩"成一个值(求和、求积、找最值、转对象等):

// reduce:把数组"汇总"成一个值
// 接收:回调(累计值, 当前元素) 和 初始值

// 求和
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum);   // 10

// 找最大值
const max = nums.reduce((m, n) => Math.max(m, n));
console.log(max);   // 4

// 数组转对象
const users = [
  { id: 1, name: "小明" },
  { id: 2, name: "小红" }
];
const userMap = users.reduce((map, u) => {
  map[u.id] = u.name;
  return map;
}, {});
console.log(userMap);
// { 1: "小明", 2: "小红" }

// 统计元素出现次数
const words = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = words.reduce((acc, w) => {
  acc[w] = (acc[w] || 0) + 1;
  return acc;
}, {});
console.log(counts);   // { apple: 3, banana: 2, cherry: 1 }

关键理解:acc(accumulator)是累计值,每次回调返回的结果会成为下一次的 acc。第二个参数 0初始值,不传就用数组第一个元素。

5. 排序 sort

sort 是 JS 数组最大的坑之一——默认按字符串 Unicode 排序,数字排序完全错乱:

// sort():默认按"字符串 Unicode"排序,数字会出问题!
const nums = [10, 5, 1, 25, 100];
nums.sort();
console.log(nums);   // [1, 10, 100, 25, 5] ⚠️ 完全错了

// 正确做法:传比较函数
nums.sort((a, b) => a - b);   // 升序
console.log(nums);   // [1, 5, 10, 25, 100]
nums.sort((a, b) => b - a);   // 降序
console.log(nums);   // [100, 25, 10, 5, 1]

// 对象数组排序
const users = [
  { name: "小明", age: 20 },
  { name: "小红", age: 22 },
  { name: "小刚", age: 19 }
];
users.sort((a, b) => a.age - b.age);    // 按年龄升序
console.log(users[0].name);   // 小刚

// reverse():反转数组
const arr = [1, 2, 3];
arr.reverse();
console.log(arr);    // [3, 2, 1]

记住:数字排序永远要传比较函数。另外 sort 会修改原数组,如果不想改原数组,先用展开运算符或 slice() 复制一份再排。

6. 其他常用方法

// 其他常用方法
const arr = [3, 1, 4, 1, 5, 9, 2, 6];

// slice(起始, 结束):截取片段(返回新数组,不改原数组)
console.log(arr.slice(2, 5));   // [4, 1, 5](不含 end)
console.log(arr.slice(-3));     // [9, 2, 6](负数从末尾算)

// concat():合并数组(返回新数组)
const merged = [1, 2].concat([3, 4]);
console.log(merged);           // [1,2,3,4]
// 推荐用展开运算符
const merged2 = [...[1, 2], ...[3, 4]];

// includes():是否包含某值
console.log(arr.includes(5));   // true

// indexOf() / lastIndexOf():找下标
console.log(arr.indexOf(1));    // 1
console.log(arr.indexOf(99));   // -1(不存在)

// join():数组 → 字符串
console.log([1, 2, 3].join("-"));   // "1-2-3"

// flat():数组扁平化
const nested = [1, [2, [3, 4]]];
console.log(nested.flat());       // [1, 2, [3, 4]](默认只展开 1 层)
console.log(nested.flat(Infinity)); // [1, 2, 3, 4](全部展开)

slice vs splice 区别(极易混淆):

7. 数组的"可变"与"不可变"

JS 数组方法分两类:

函数式编程/React 开发推崇不可变(immutable)——永远不改原数组,而是返回新数组。这样代码更可预测,也方便调试。习惯用 map/filter/... 替代 push/splice

// ❌ 老风格:改原数组
const arr = [1, 2, 3];
arr.push(4);

// ✅ 函数式:不改原数组
const arr2 = [1, 2, 3];
const newArr = [...arr2, 4];
console.log(arr2);     // [1,2,3](不变)
console.log(newArr);   // [1,2,3,4]

8. 类数组与解构

// 数组解构
const [first, second, third] = [10, 20, 30];
console.log(first, second, third);   // 10 20 30

// 跳过中间元素
const [a, , c] = [1, 2, 3];
console.log(a, c);                    // 1 3

// 剩余元素
const [head, ...rest] = [1, 2, 3, 4];
console.log(head, rest);              // 1 [2,3,4]

// 交换两个变量(超实用!)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y);                    // 2 1

// 类数组转真数组(arguments、NodeList)
function test() {
  const arr = [...arguments];         // 用展开
  const arr2 = Array.from(arguments); // 用 Array.from
  console.log(arr, arr2);
}
test(1, 2, 3);

9. 实战:商品列表处理

const products = [
  { id: 1, name: "键盘", price: 199, stock: 10 },
  { id: 2, name: "鼠标", price: 89, stock: 0 },
  { id: 3, name: "显示器", price: 1599, stock: 5 },
  { id: 4, name: "耳机", price: 499, stock: 20 }
];

// 1. 找所有有库存的商品
const available = products.filter(p => p.stock > 0);
console.log(available.length);    // 3

// 2. 提取所有商品名
const names = products.map(p => p.name);
console.log(names);    // ["键盘","鼠标","显示器","耳机"]

// 3. 计算总库存价值
const totalValue = products.reduce((sum, p) => sum + p.price * p.stock, 0);
console.log(totalValue);    // 199*10 + 0 + 1599*5 + 499*20 = 21480

// 4. 按价格排序,取前 3 贵
const top3 = [...products].sort((a, b) => b.price - a.price).slice(0, 3);
console.log(top3.map(p => p.name));    // ["显示器","耳机","键盘"]

// 5. 转成 id → product 的映射
const productMap = products.reduce((m, p) => (m[p.id] = p, m), {});
console.log(productMap[3].name);    // 显示器

这是真实业务里最常见的操作——接口返回一个数组,你要过滤、转换、汇总、查找、排序。熟练掌握这些方法,日常开发就游刃有余。

小结

下一篇我们看 JS 的字符串方法。

← 上一篇 JavaScript 对象

下一篇 JavaScript 字符串

✈️💬