数组
有序列表,管理一组数据的利器
🎯 学习目标
学完本章,你将能够:
- 能够使用 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 默认按字符串排序,数字排序需传比较函数
下一章
数组掌握了基本操作。下一章我们深入函数与作用域,学习更多高级用法——回调、递归、异步编程的基石。