侧边栏壁纸
  • 累计撰写 240 篇文章
  • 累计创建 292 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

JavaScript数组专题之5:数组的迭代与转换

DGF
DGF
2025-10-03 / 0 评论 / 0 点赞 / 6 阅读 / 0 字

JS 数组系列专题文章目录

入门篇

第 1 篇:认识 JavaScript 数组——从入门到操作基础
👉 介绍数组的概念、创建方式、索引与长度的使用,以及最基础的遍历方法。适合刚接触数组的新手。

第 2 篇:数组的增删改全解析(push、pop、splice)
👉 系统讲解数组的增删改方法,包括 pushpopshiftunshiftsplicefill,并结合小案例说明。

常用方法篇

第 3 篇:数组的查找与判断方法大全
👉 详细介绍如何在数组中查找元素,包括 indexOflastIndexOfincludesfindfindIndexsomeevery 等。

第 4 篇:数组的排序与合并技巧(sort、concat、slice)
👉 讲解数组排序与反转的方法,以及合并、切片、字符串化的常见操作,重点讲解 sort 排序中的注意点。

进阶篇

第 5 篇:数组的迭代与转换(map、filter、reduce 全解)
👉 深入剖析高阶方法 mapfilterreduce,包括实际应用场景:数组求和、数据统计、扁平化处理等。

第 6 篇:数组的实用技巧与案例集锦
👉 介绍常见的数组处理技巧:去重(多种写法对比)、扁平化(flat 与递归实现)、字符串与数组互转、对象数组排序。

高阶篇

第 7 篇:类数组与稀疏数组——容易忽略的细节
👉 解释什么是类数组(argumentsNodeList),如何转换为真数组,稀疏数组的特性和坑点。

第 8 篇:不可变数组操作与函数式编程思路
👉 如何在不修改原数组的前提下进行操作(扩展运算符、concatmap 等),结合函数式编程的思想讲解。

实战 & 面试篇

第 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)。
  • 不能用 breakreturn 提前跳出循环。

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:想要转换的类数组 / 可迭代对象,比如字符串、argumentsNodeListSet 等。
  • 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]),很多方法会跳过空位(不会调用回调),包括 forEachmapfilterreducesomeevery 等。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 才能并发等待它们。

0

评论区