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、分页实现,涵盖面试常考题。
1. 去重(去除重复元素)
1.1 基本类型(数字 / 字符串 / 布尔)
最简单、最快的办法是 Set:
const arr = [1, 2, 2, 3, 1];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3]
- 原理:
Set只保留首次出现的值,保留插入顺序。 - 复杂度:O(n)。适合大多数场景。
1.2 兼容 NaN 情况
Set 能正确处理 NaN(把多个 NaN 视为同一个),而 indexOf 不行:
const a = [NaN, NaN];
console.log([...new Set(a)]); // [NaN]
1.3 对象数组(按某个 key 去重)
对象不能直接用 Set 去重(比较引用)。常用方法:用 Map 或用一个键(id、name)做标识:
function uniqueByKey(arr, key) {
const seen = new Map();
return arr.filter(item => {
const k = item[key];
if (seen.has(k)) return false;
seen.set(k, true);
return true;
});
}
// 示例
const users = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 1, name: 'A2' } // id 重复
];
console.log(uniqueByKey(users, 'id'));
// [{id:1,name:'A'}, {id:2,name:'B'}]
- 适合场景:数据有稳定唯一字段(如
id、email)时最靠谱。
1.4 对象按内容去重(无唯一 key)
可用 JSON.stringify 作为简易方案(注意属性顺序会影响结果):
function uniqueByJSON(arr) {
const seen = new Set();
return arr.filter(item => {
const key = JSON.stringify(item);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
- 注意:不适合含有函数、
undefined、Date等复杂类型,且对象属性顺序不同会被认为不同。生产环境建议用规范化函数或指定 key。
2. 扁平化(Flatten)
2.1 用内置 flat
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat(1)); // [1, 2, [3, [4]]]
console.log(nested.flat(2)); // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)); // [1,2,3,4]
depth参数默认1;Infinity表示完全扁平化。
2.2 递归实现(自定义深度)
function flatten(arr, depth = 1) {
const res = [];
for (const item of arr) {
if (Array.isArray(item) && depth > 0) {
res.push(...flatten(item, depth - 1));
} else {
res.push(item);
}
}
return res;
}
// 用法
flatten([1, [2, [3]]], 2); // [1,2,3]
- 优点:兼容旧环境或做特殊处理(比如在展开时做过滤、转换等)。
3. 数组 ⇄ 字符串(split / join)
3.1 join 把数组变成字符串
const arr = ['apple', 'banana', 'orange'];
console.log(arr.join(', ')); // "apple, banana, orange"
console.log([].join('-')); // ""(空数组变空字符串)
join(separator)默认separator是,。
3.2 split 把字符串变成数组
const s = "a,b,c";
console.log(s.split(',')); // ["a","b","c"]
console.log("".split(',')); // [""] 注意:空字符串返回 [""],不是 []
3.3 简易 CSV 注意点
split(',') 对简单 CSV OK,但不能应对带引号、带逗号的字段。复杂 CSV 请用专门解析库(如 PapaParse)。(提示:这也是常见坑)
4. 对象数组排序技巧(多字段 & locale 比较)
4.1 按数字字段排序
students.sort((a, b) => a.score - b.score); // 升序
4.2 多字段排序(先按 age,再按 name)
students.sort((a, b) => {
const diff = a.age - b.age;
if (diff !== 0) return diff; // 年龄不同,按年龄
return a.name.localeCompare(b.name); // 年龄相同,按名字
});
localeCompare可处理中文/大小写/本地化排序,支持localeCompare(other, locale, options)的高级用法。
5. 分页 / 切块(chunk)
5.1 实现 chunk(arr, size)
function chunk(arr, size) {
if (size <= 0) throw new Error('size must be > 0');
const res = [];
for (let i = 0; i < arr.length; i += size) {
res.push(arr.slice(i, i + size));
}
return res;
}
// 示例
chunk([1,2,3,4,5], 2); // [[1,2], [3,4], [5]]
- 使用场景:分页显示、分批上传、批处理。
5.2 根据页码获取该页内容
function getPage(arr, page = 1, pageSize = 10) {
const total = Math.ceil(arr.length / pageSize);
const p = Math.max(1, Math.min(page, total));
const start = (p - 1) * pageSize;
return {
page: p,
pageSize,
total,
data: arr.slice(start, start + pageSize),
};
}
6. 集合运算:并 / 交 / 差(针对原始值)
const a = [1,2,3];
const b = [2,3,4];
// 并集
const union = [...new Set([...a, ...b])]; // [1,2,3,4]
// 交集
const setB = new Set(b);
const intersection = a.filter(x => setB.has(x)); // [2,3]
// 差(a - b)
const difference = a.filter(x => !setB.has(x)); // [1]
- 复杂度:利用
Set,交/差 O(n)。
对于对象数组可用 key 提取或自定义比较。
7. 随机打乱(Fisher–Yates 洗牌)
正确且均匀的做法是 Fisher–Yates:
function shuffle(array) {
const arr = array.slice(); // 复制,不破坏原数组
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // [0, i]
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
- 注意:不要用
sort(() => Math.random() - 0.5)——错误且不均匀。
8. 浅拷贝 / 深拷贝
8.1 浅拷贝(只复制第一层)
const a = [1,2,3];
const b = [...a]; // 或 a.slice()
- 适合元素是原始类型的数组。若元素是对象,仍是引用。
8.2 深拷贝(常见方法)
- 快速但有局限:
JSON.parse(JSON.stringify(obj))(丢失undefined、函数、Date→字符串、RegExp等) - 标准且功能强:
structuredClone(obj)(现代浏览器 / Node 支持)
const deep = structuredClone(obj);
- 如果环境不支持
structuredClone,可以用lodash.cloneDeep。
9. 性能
- 避免在循环里反复做 O(n) 操作:例如在循环里
.includes(),改用先把目标转为Set。 - 减少中间数组:链式
filter().map().reduce()虽语义清晰,但会创建中间数组;在超大数组或性能敏感场景,可用一次reduce完成多步。 - 原地操作:
sort()/reverse()会修改原数组,若要保留原件先slice()或[...arr]复制一份。 - 使用合适的数据结构:当需要频繁查找时,把数组转换成
Map/Set能显著提高性能。
10. 综合实战:去重 + 排序 + 分页(完整示例)
把一组用户 ID(可能重复)处理成“去重、按 id 升序、取第 2 页(每页 3 条)”:
function process(ids, page = 2, pageSize = 3) {
// 去重并排序
const uniqueSorted = [...new Set(ids)].sort((a, b) => a - b);
// 分页
const start = (page - 1) * pageSize;
return uniqueSorted.slice(start, start + pageSize);
}
const ids = [5,2,3,2,8,1,5,10,7];
console.log(process(ids, 2, 3)); // 如果 uniqueSorted = [1,2,3,5,7,8,10], 页2返回 [5,7,8]
评论区