JS 数组系列专题文章目录
入门篇
第 1 篇:认识 JavaScript 数组——从入门到操作基础
👉 介绍数组的概念、创建方式、索引与长度的使用,以及最基础的遍历方法。适合刚接触数组的新手。第 2 篇:数组的增删改全解析(push、pop、splice)
👉 系统讲解数组的增删改方法,包括push
、pop
、shift
、unshift
、splice
、fill
,并结合小案例说明。常用方法篇
第 3 篇:数组的查找与判断方法大全
👉 详细介绍如何在数组中查找元素,包括indexOf
、lastIndexOf
、includes
、find
、findIndex
、some
、every
等。第 4 篇:数组的排序与合并技巧(sort、concat、slice)
👉 讲解数组排序与反转的方法,以及合并、切片、字符串化的常见操作,重点讲解sort
排序中的注意点。进阶篇
第 5 篇:数组的迭代与转换(map、filter、reduce 全解)
👉 深入剖析高阶方法map
、filter
、reduce
,包括实际应用场景:数组求和、数据统计、扁平化处理等。第 6 篇:数组的实用技巧与案例集锦
👉 介绍常见的数组处理技巧:去重(多种写法对比)、扁平化(flat
与递归实现)、字符串与数组互转、对象数组排序。高阶篇
第 7 篇:类数组与稀疏数组——容易忽略的细节
👉 解释什么是类数组(arguments
、NodeList
),如何转换为真数组,稀疏数组的特性和坑点。第 8 篇:不可变数组操作与函数式编程思路
👉 如何在不修改原数组的前提下进行操作(扩展运算符、concat
、map
等),结合函数式编程的思想讲解。实战 & 面试篇
第 9 篇:手写数组常用方法(map、filter、reduce)
👉 从源码角度模拟实现常见数组方法,帮助加深理解,提升面试竞争力。第 10 篇:数组实战应用与高频面试题精选
👉 总结数组在实战中的应用:两数之和、数组旋转、最大最小值查找、groupBy
、分页实现,涵盖面试常考题。
上一篇我们聊了排序和合并,这次要进入数组的核心技能之一:迭代与转换。
说白了,就是“如何一口气把数组里的每一项都处理一遍,并产出新东西”。
forEach
像是 逐个打卡:你检查每个人到没到场,但不会生成新的名单。map
像是 批量换装:一堆人进店,把每个人衣服都换了,出来还是同样人数的新队伍。filter
像是 筛选:你说“身高 180cm 以上的留下”,于是留下来的就是一个新小队伍。reduce
像是 聚合:大家把钱放在一起,最后算总额。
...
1. forEach()
—— 遍历,不返回新数组
语法:
array.forEach(callback(element, index, array), thisArg)
element
:当前元素的值index
:当前元素的下标array
:正在遍历的原数组本身thisArg
(可选):指定回调函数里this
的值
例子:点名签到
const students = ["小明", "小红", "小李"];
students.forEach((name, index, arr) => {
console.log(`第${index + 1}个学生:${name}`);
console.log(`完整数组:`, arr);
});
👉 输出:
第1个学生:小明
完整数组:[ '小明', '小红', '小李' ]
第2个学生:小红
完整数组:[ '小明', '小红', '小李' ]
第3个学生:小李
完整数组:[ '小明', '小红', '小李' ]
⚠️ 注意:
forEach
没有返回值(返回undefined
)。- 不能用
break
或return
提前跳出循环。
2. map()
—— 一对一转换,返回新数组
语法:
array.map(callback(element, index, array), thisArg)
element
:当前元素的值index
:当前元素下标array
:原数组thisArg
:指定回调里的this
例子:打折处理价格
const prices = [100, 200, 300];
const discounted = prices.map((price, index) => {
console.log(`第${index + 1}个商品原价:${price}`);
return price * 0.8; // 打八折
});
console.log(discounted); // [80, 160, 240]
👉 特点:
- 返回一个新数组,长度与原数组相同。
- 每个元素被替换成
callback
的返回值。
3. filter()
—— 条件筛选,返回子数组
语法:
array.filter(callback(element, index, array), thisArg)
例子:筛选出及格的学生成绩
const scores = [45, 60, 85, 90, 30];
const passed = scores.filter((score, index, arr) => {
console.log(`第${index + 1}个分数:${score}`);
return score >= 60; // 条件
});
console.log(passed); // [60, 85, 90]
👉 特点:
- 返回满足条件的新数组。
- 原数组不会被修改。
4. reduce()
—— 汇总成单一值
语法:
array.reduce(callback(accumulator, currentValue, index, array), initialValue)
accumulator
:累加器,保存上一次回调的返回值currentValue
:当前遍历到的元素index
:当前元素下标array
:原数组initialValue
(可选):初始值。如果不给,accumulator
会默认是第一个元素,遍历从第二个元素开始
例子 1:求和
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, cur, index, arr) => {
console.log(`第${index}次:acc=${acc}, cur=${cur}`);
return acc + cur;
}, 0);
console.log("总和:", sum); // 10
👉 执行过程:
- 初始
acc=0
- 第0次:acc=0, cur=1 → acc=1
- 第1次:acc=1, cur=2 → acc=3
- 第2次:acc=3, cur=3 → acc=6
- 第3次:acc=6, cur=4 → acc=10
例子 2:统计水果数量
const fruits = ["苹果", "香蕉", "苹果", "梨"];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // { 苹果: 2, 香蕉: 1, 梨: 1 }
好的!那我来给 reduce
方法补上两个经典、实用的 分组 和 合并对象 的案例,并且会把参数解释清楚。
例子 3:数组分组(grouping)
假设我们有一堆学生,每个人都有名字和年级。我们想按年级把他们分组。
const students = [
{ name: "小明", grade: "一年级" },
{ name: "小红", grade: "二年级" },
{ name: "小刚", grade: "一年级" },
{ name: "小丽", grade: "三年级" },
{ name: "小强", grade: "二年级" }
];
// 用 reduce 来分组
const grouped = students.reduce((acc, student) => {
// student 是当前遍历的元素
const { grade } = student; // 取出年级
if (!acc[grade]) {
acc[grade] = []; // 如果这个年级还没有分组,就先建一个空数组
}
acc[grade].push(student); // 把学生放进对应年级里
return acc;
}, {}); // 初始值是一个空对象
console.log(grouped);
👉 输出结果:
{
"一年级": [
{ name: "小明", grade: "一年级" },
{ name: "小刚", grade: "一年级" }
],
"二年级": [
{ name: "小红", grade: "二年级" },
{ name: "小强", grade: "二年级" }
],
"三年级": [
{ name: "小丽", grade: "三年级" }
]
}
📌 参数说明:
acc
(累加器):每一步的“分组结果”,一开始是{}
空对象。student
:数组里当前正在处理的学生对象。grade
:根据student.grade
来决定放到哪个班级。
例子 4:合并对象数组
有时候我们拿到的数据是“碎片化”的,需要合并成一个整体。比如统计不同水果的数量:
const fruits = [
{ name: "苹果", count: 2 },
{ name: "香蕉", count: 5 },
{ name: "苹果", count: 3 },
{ name: "橘子", count: 4 },
{ name: "香蕉", count: 2 }
];
// 用 reduce 合并相同水果的数量
const merged = fruits.reduce((acc, item) => {
const { name, count } = item; // 解构出水果名和数量
if (!acc[name]) {
acc[name] = 0; // 初始化为 0
}
acc[name] += count; // 累加数量
return acc;
}, {}); // 初始值是 {}
console.log(merged);
👉 输出结果:
{
"苹果": 5,
"香蕉": 7,
"橘子": 4
}
📌 参数说明:
acc
:用来记录每种水果的总数,初始是{}
。item
:当前遍历的水果对象,比如{ name: "苹果", count: 2 }
。name
:水果名,作为acc
的 key。count
:水果数量,作为累加的值。
这个逻辑就像你去超市买水果,收银员会帮你把 同一种水果的数量合并计算,最后结账更方便。
⚠️ 建议:永远写上 initialValue
,避免空数组报错。
5. reduceRight()
—— 从右向左汇总
和 reduce
一样,只是遍历方向相反。
例子:拼接字符串
const words = ["我", "是", "程序员"];
const result = words.reduceRight((acc, word) => acc + word, "");
console.log(result); // 程序员是我
6. flat()
—— 数组扁平化
语法:
array.flat(depth)
depth
:展开层数,默认是 1。
例子:
const arr = [1, [2, [3, [4]]]];
console.log(arr.flat()); // [1, 2, [3, [4]]]
console.log(arr.flat(2)); // [1, 2, 3, [4]]
console.log(arr.flat(Infinity)); // [1, 2, 3, 4]
7. flatMap()
—— 先 map
,再 flat(1)
例子:拆分句子里的单词
const sentences = ["hello world", "js array"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words); // ["hello", "world", "js", "array"]
8. Array.from()
—— 把类数组/可迭代对象转成数组
Array.from()
是 ES6 提供的一个好东西,主要功能是:
👉 把类数组对象(array-like)或可迭代对象(iterable,比如字符串、Set、Map)转换成真正的数组。
📌 语法
Array.from(arrayLike, mapFn, thisArg)
参数说明:
arrayLike
:想要转换的类数组 / 可迭代对象,比如字符串、arguments
、NodeList
、Set
等。mapFn
(可选):类似于数组的map
,对每个元素做处理后返回。thisArg
(可选):指定mapFn
内部this
的指向。
示例 1:把字符串拆成数组
const menu = "红烧肉,宫保鸡丁,鱼香茄子";
const dishes = Array.from(menu); // 拆成一个个字符
console.log(dishes);
// ["红", "烧", "肉", ",", "宫", "保", "鸡", "丁", ",", "鱼", "香", "茄", "子"]
// 如果想按逗号分菜,还是用 split 更合适:
console.log(menu.split(","));
// ["红烧肉", "宫保鸡丁", "鱼香茄子"]
👉 用 Array.from()
时,它会逐字符拆解。
示例 2:生成一组数字(比如彩票号码)
生活场景:你要模拟生成一张彩票(6 个号码),用 Array.from
一行就搞定。
// 生成 [1, 2, 3, 4, 5, 6]
const numbers = Array.from({ length: 6 }, (_, i) => i + 1);
console.log(numbers);
// [1, 2, 3, 4, 5, 6]
// 模拟随机彩票
const lottery = Array.from({ length: 6 }, () => Math.floor(Math.random() * 33) + 1);
console.log(lottery);
// [12, 5, 27, 31, 8, 20] (每次运行都可能不同)
📌 这里 { length: 6 }
是一个类数组对象(有 length,没有真正的元素),Array.from
会根据 length
生成对应长度的数组。
(_, i) => i + 1
:忽略第一个参数(值),用索引i
来生成 1~6。
示例 3:成绩换算(mapFn 参数的妙用)
const scores = [60, 72, 88, 95];
// 用 Array.from 直接映射
const updatedScores = Array.from(scores, score => score + 5);
console.log(updatedScores);
// [65, 77, 93, 100]
📌 这里的 mapFn
类似于 map
,但是直接内置到 Array.from
里,省一步操作。
示例 4:处理类数组对象(arguments)
function collectAnswers() {
// arguments 是类数组对象
const arr = Array.from(arguments);
return arr.sort();
}
console.log(collectAnswers("小明", "小红", "小刚"));
// ["小刚", "小明", "小红"]
📌 arguments
并不是一个真正的数组,不能直接用 map/sort
,所以得用 Array.from()
转换。
示例 5:去重(结合 Set)
const rawData = ["苹果", "香蕉", "苹果", "橘子", "香蕉", "苹果"];
const uniqueData = Array.from(new Set(rawData));
console.log(uniqueData);
// ["苹果", "香蕉", "橘子"]
📌 原理:
Set
会自动去重。Array.from()
把Set
再转成数组。
9. 其他辅助方法
在 JavaScript 里,数组本质上是“可迭代对象”。
有时候我们需要 遍历数组的索引、值,或者 索引 + 值成对出现。
👉 这时候就轮到 keys()
、values()
、entries()
就很好用了。
1. keys() —— 遍历数组索引
const fruits = ["苹果", "香蕉", "橘子"];
for (let key of fruits.keys()) {
console.log(key);
}
// 输出:0, 1, 2
📌 说明:
keys()
返回一个 索引迭代器。- 上面就是把数组的“下标”一个个打印出来。
2. values() —— 遍历数组值
生活类比:点菜的时候只报菜名,不管是第几号菜。
for (let value of fruits.values()) {
console.log(value);
}
// 输出:苹果, 香蕉, 橘子
📌 说明:
values()
返回的是数组的 值的迭代器。- 常见场景就是只关心内容,不管顺序。
3. entries() —— 遍历索引 + 值
生活类比:像点菜时同时说“我要第 2 道 —— 宫保鸡丁”。既要菜号也要菜名。
for (let [index, value] of fruits.entries()) {
console.log(index, value);
}
// 输出:
// 0 苹果
// 1 香蕉
// 2 橘子
📌 说明:
entries()
返回一个 键值对迭代器,形如[索引, 值]
。- 解构
[index, value]
后可以很方便地同时拿到。
10. 稀疏数组(holes)与上述方法的行为
对于稀疏数组(比如 const a = [1, , 3]
),很多方法会跳过空位(不会调用回调),包括 forEach
、map
、filter
、reduce
、some
、every
等。map
在结果数组中也会保留“洞”(位置上的元素未被回调覆盖)。
示例:
const a = [1, , 3];
a.forEach((x, i) => console.log(i, x)); // 只打印 0 和 2
11. 链式调用与性能提醒
常见写法:
const result = arr
.filter(x => x > 0)
.map(x => x * 2)
.reduce((a, b) => a + b, 0);
优点:可读性强、表达清晰。
缺点:每一步会生成中间数组(filter
-> intermediate, map
-> another),在超大数组或性能敏感场景下可能有显著开销。
优化策略:
- 若担心性能,可用
reduce
一次完成多步转换(减少中间数组)。 - 或者在 ES2020+ 使用
flatMap
来替代map().flat()
的场景。
示例(用 reduce
合并 filter+map):
const sum = arr.reduce((acc, x) => {
if (x > 0) acc += x * 2;
return acc;
}, 0);
12. 异步操作与 map
的常见陷阱
很多人写 async
map 时犯错:
// 错误示范(得到的是 Promise[])
const res = arr.map(async x => await fetchAndProcess(x));
// 如果要等待所有完成:
const results = await Promise.all(arr.map(async x => await fetchAndProcess(x)));
记住:map(async...)
返回的是 Promise[]
,要用 Promise.all
才能并发等待它们。
评论区