数组

有序列表,管理一组数据的利器

🎯 学习目标

学完本章,你将能够:

  • 能够使用 push/pop/shift/unshift/splice 对数组进行增删改操作
  • 能够使用 map、filter、reduce 三个核心方法进行数据转换和聚合
  • 能够使用 find、some、every 进行条件查找和判断
  • 能够使用数组解构和展开运算符处理数组数据
  • 能够正确使用 sort 方法排序,理解默认排序的坑

📌 有编程基础?数组的基本操作你会。但 map / filter / reduce 是 JS 函数式编程的灵魂——后面 React、异步处理全靠它们,值得精读。另外注意 sort() 默认按字符串排序([10, 2, 1] 排出来是 [1, 10, 2]),这是 JS 的经典坑。

你有没有遇到过

一个班 30 个学生的成绩,要排序、找最高分、算平均分——难道建 30 个变量?

比如你需要记录购物车里的商品、处理用户的标签列表、分析一组传感器数据。数据量不确定,可能 3 条,也可能 3000 条。

你不可能为每一条数据建一个变量。数组就是用来存放一组有序数据的容器。一个变量装下所有数据,用索引编号来访问。

什么是数组

定义 → 白话 → 代码

定义:数组是一种有序的数据集合,通过数字索引(从 0 开始)访问每个元素。

白话:数组就像一排带编号的储物柜。0 号柜放第一个东西,1 号柜放第二个,以此类推。你想拿第三个东西,就去开 2 号柜。

这个比喻只是为了帮你建立直观感受。在计算机内部,数组实际上是一块连续的内存空间,引擎通过起始地址加偏移量来计算每个元素的位置(V8 引擎对 JS 数组做了优化,不一定是物理连续的)。

let scores = [85, 92, 78, 96, 88];

console.log(scores[0]);          // 85(第一个)
console.log(scores.length);      // 5
console.log(scores.at(-1));      // 88(最后一个)

创建数组

字面量最常用,其他方式了解即可

// 字面量(最常用)
let arr1 = [1, 2, 3, 4, 5];

// 从字符串创建
let chars = Array.from("hello");
// ["h", "e", "l", "l", "o"]

// 生成序列
let range = Array.from({ length: 5 }, (_, i) => i);
// [0, 1, 2, 3, 4]

// 检查是否为数组
console.log(Array.isArray([1, 2])); // true
console.log(Array.isArray("123"));  // false
常见错误:new Array(5) 创建的不是 [5],而是长度为 5 的空数组。想创建只有一个元素 5 的数组,用 [5] 或 Array.of(5)。

增删操作

尾部和头部各一对方法

let arr = [1, 2, 3];

// 尾部操作
arr.push(4);       // 末尾添加,返回新长度
let last = arr.pop(); // 末尾删除,返回删除的值

// 头部操作
arr.unshift(0);     // 开头添加
let first = arr.shift(); // 开头删除

// 任意位置操作:splice
let nums = [1, 2, 3, 4, 5];
nums.splice(2, 1);       // 从索引2删1个 → [1,2,4,5]
nums.splice(1, 0, "a");  // 在索引1插入 → [1,"a",2,4,5]

栈(后进先出)

push + pop

像叠盘子:最后放的先拿走。

队列(先进先出)

push + shift

像排队:先来的先走。

检查点练习

练习一

任务:创建数组 [10, 20, 30, 40]。在末尾加 50,在开头加 0,然后删除第二个元素(20),最后输出数组。

// 你的代码:

参考答案

let arr = [10, 20, 30, 40];
arr.push(50);           // [10,20,30,40,50]
arr.unshift(0);         // [0,10,20,30,40,50]
arr.splice(2, 1);       // 删除索引2(即20)
console.log(arr);
// [0, 10, 30, 40, 50]

Part 2: 遍历与高阶方法

数组最强大的地方不只是存储,而是"对每个元素做点什么"

已经会用 push、pop 增删数据了,但如果要对 100 个元素逐个处理呢?手动写 100 行显然不现实。遍历是数组操作的基石,而 map、filter、reduce 这些高阶方法则是日常开发中出场率最高的工具。学完这一部分,你就能用一行代码完成"筛选→变换→汇总"整条链路。

遍历数组

三种方式各有适用场景

let arr = [10, 20, 30];

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

// for...of:只需值时(推荐)
for (let item of arr) {
    console.log(item);
}

// forEach:简洁,但不能 break
arr.forEach((item, index) => {
    console.log(index, item);
});

for...of 配合 .entries() 可以同时拿到索引和值:for (let [i, v] of arr.entries())。

map 映射

把数组里的每个元素"变形"一次

定义:map 对每个元素执行函数,返回一个等长的新数组。

白话:就像工厂流水线上的"改造站"。每个产品经过站点时被改造一次,然后进入下一条传送带。原产品不变。

let nums = [1, 2, 3, 4, 5];

// 每个元素翻倍
let doubled = nums.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 转成对象数组
let users = nums.map((id, i) => ({ id, value: id * 10 }));
// [{id:1, value:10}, {id:2, value:20}, ...]

map 不改变原数组,它返回新数组。这是函数式编程的核心思想。

filter 过滤

留下符合条件的,丢掉其余的

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 只保留偶数
let evens = nums.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// 只保留大于5的
let big = nums.filter(n => n > 5);
console.log(big);   // [6, 7, 8, 9, 10]

回调函数返回 true 的元素被保留,返回 false 的被过滤掉。同样不改变原数组。

reduce 归约

把数组"压缩"成一个值

定义:reduce 对数组每个元素执行累积计算,最终得到一个结果。

let nums = [1, 2, 3, 4, 5];

// 求和
let sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// 找最大值
let max = nums.reduce((a, b) => a > b ? a : b);
console.log(max); // 5

// 按首字母分组
let words = ["apple", "banana", "avocado"];
let grouped = words.reduce((acc, word) => {
    let key = word[0];
    (acc[key] = acc[key] || []).push(word);
    return acc;
}, {});
// { a: ["apple","avocado"], b: ["banana"] }

reduce 实战

不只是求和——reduce 的四种常见模式

// 模式1:统计频次
let fruits = ["苹果", "香蕉", "苹果", "橙子", "香蕉", "苹果"];
let count = fruits.reduce((acc, f) => {
    acc[f] = (acc[f] || 0) + 1;
    return acc;
}, {});
// { 苹果: 3, 香蕉: 2, 橙子: 1 }

// 模式2:数组去重
let dup = [1, 2, 2, 3, 3, 3];
let unique = dup.reduce((acc, n) => {
    if (!acc.includes(n)) acc.push(n);
    return acc;
}, []);
// [1, 2, 3](或者直接用 [...new Set(dup)])

// 模式3:管道——组合多个操作
let pipeline = [
    (arr) => arr.filter(n => n > 0),
    (arr) => arr.map(n => n * 2),
    (arr) => arr.reduce((s, n) => s + n, 0)
];
let result = pipeline.reduce((val, fn) => fn(val), [-1, 2, 3, -4, 5]);
// 20(过滤→翻倍→求和)

记住 reduce 的本质:拿一个"累积器",遍历数组,每一步更新累积器,最后返回它。累积器可以是数字、对象、数组——任何东西。

find / some / every

查找与判断的三个利器

let nums = [1, 2, 3, 4, 5, 6];

// find:找到第一个满足条件的元素
let found = nums.find(n => n > 3);
console.log(found); // 4

// findIndex:找到它的索引
let idx = nums.findIndex(n => n > 3);
console.log(idx);   // 3

// some:是否有至少一个满足条件
let hasEven = nums.some(n => n % 2 === 0);
console.log(hasEven); // true

// every:是否全部满足条件
let allPositive = nums.every(n => n > 0);
console.log(allPositive); // true

flat 与 flatMap

拍平嵌套数组

// flat:按指定深度拍平
let nested = [1, [2, 3], [4, [5, 6]]];
nested.flat();           // [1, 2, 3, 4, [5, 6]]
nested.flat(Infinity);   // [1, 2, 3, 4, 5, 6]

// flatMap:先 map 再 flat(1)
let sentences = ["hello world", "foo bar"];
let allWords = sentences.flatMap(s => s.split(" "));
console.log(allWords); // ["hello","world","foo","bar"]

检查点练习

练习二

任务:给定数组 [12, 5, 8, 130, 44, 3, 27]。用链式调用筛选出大于 10 的数,然后对每个数翻倍,最后求和。

// 你的代码:

参考答案

let nums = [12, 5, 8, 130, 44, 3, 27];
let result = nums
    .filter(n => n > 10)    // [12, 130, 44, 27]
    .map(n => n * 2)        // [24, 260, 88, 54]
    .reduce((a, b) => a + b, 0);
console.log(result); // 426

Part 3: 排序与解构

排序有坑,解构是捷径

[10, 1, 21, 2].sort() 的结果不是 [1, 2, 10, 21],你信吗?默认排序按字符串比较,这是数组里最容易踩的坑之一。同时,你会学到解构赋值——一种从数组里"一口气"提取多个值的语法,交换变量再也不需要临时变量了。

排序的坑

sort 默认按字符串排,数字会乱序

// 陷阱!默认按字符串排序
let nums = [10, 1, 21, 2];
nums.sort();
console.log(nums); // [1, 10, 2, 21] — 错了!

// 数值升序(正确写法)
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 10, 21]

// 数值降序
nums.sort((a, b) => b - a);

// 对象数组排序
let users = [
    { name: "张三", age: 25 },
    { name: "李四", age: 20 },
    { name: "王五", age: 30 }
];
users.sort((a, b) => a.age - b.age);
症状:[10, 1, 21, 2].sort() 得到 [1, 10, 2, 21],不是 [1, 2, 10, 21]。
原因:sort() 默认把元素转成字符串再比较。"10" 比 "2" 小(逐字符比较)。
修复:排序数字时一定要传比较函数 (a, b) => a - b。

查找与反转

indexOf、includes、reverse

let arr = [1, 2, 3, 2, 1];

// 查找索引
console.log(arr.indexOf(2));       // 1
console.log(arr.lastIndexOf(2));   // 3
console.log(arr.indexOf(99));      // -1

// 是否包含
console.log(arr.includes(3));      // true

// 反转(修改原数组)
arr.reverse();
console.log(arr); // [1, 2, 3, 2, 1]

// ES2023 不修改原数组的方法
let nums = [3, 1, 2];
let sorted = nums.toSorted((a, b) => a - b);
let reversed = nums.toReversed();
console.log(nums);    // [3, 1, 2](原数组不变)

数组解构

一口气提取多个元素

// 基本解构
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 跳过元素
let [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3

// 默认值
let [x, y, z = 10] = [1, 2];
console.log(z); // 10

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

// 交换变量(不用临时变量)
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2 1

综合练习

练习三:成绩分析

任务:给定学生成绩数组,用数组方法链式调用完成以下分析。

let students = [
    { name: "张三", score: 85 },
    { name: "李四", score: 42 },
    { name: "王五", score: 91 },
    { name: "赵六", score: 67 },
    { name: "钱七", score: 38 }
];
// 1. 筛选及格(≥60)的学生
// 2. 按分数降序排列
// 3. 输出格式:"王五: 91, 张三: 85, 赵六: 67"
// 4. 计算全班平均分(保留1位小数)

参考答案

// 及格学生按分数降序
let passed = students
    .filter(s => s.score >= 60)
    .sort((a, b) => b.score - a.score);
console.log(passed.map(s => `${s.name}: ${s.score}`).join(", "));
// "王五: 91, 张三: 85, 赵六: 67"

// 全班平均分
let avg = (students.reduce((s, st) => s + st.score, 0) / students.length).toFixed(1);
console.log(`平均分: ${avg}`); // 64.6

常见错误诊断

症状一:对数组调用 map 或 filter 后,原数组也变了
原因:map/filter 本身不会改原数组。但如果回调函数里直接修改了对象元素,原数组里的对象会受影响。
修复:在回调函数里返回新对象,而不是修改原对象。
症状二:sort() 排数字得到奇怪结果
原因:默认字符串排序。"10" < "2"。
修复:永远传比较函数 (a, b) => a - b。
症状三:splice 删除元素后索引错位
原因:splice 在原地修改数组,删除一个元素后后面所有元素索引前移。
修复:需要删除多个元素时,从后往前遍历,或者用 filter 创建新数组。

📝 要点总结

本章核心知识点

增删操作

push/pop/shift/unshift/splice 操作数组

三大核心

map 映射、filter 过滤、reduce 归约

查找判断

find 找元素、some 有满足、every 全满足

解构展开

解构赋值和展开运算符简化数组操作

排序陷阱

sort 默认按字符串排序,数字排序需传比较函数

下一章

数组掌握了基本操作。下一章我们深入函数与作用域,学习更多高级用法——回调、递归、异步编程的基石。

1 / X