数组 (Array)
1. 核心概念与特性
| 特性 | 描述 |
|---|---|
| 动态大小 | JS 数组无需预先声明长度,它会随着元素的增加自动扩容。 |
| 异构性 | 同一个数组可以容纳不同类型的数据(如 [1, "hello", {a: 1}, null])。 |
| 索引机制 | 从 0 开始。通过 arr[index] 访问元素,时间复杂度为 $O(1)$。 |
| 本质 | 数组其实是特殊的对象。typeof [] 返回 "object"。判断数组的正确方式是 Array.isArray(arr)。 |
2. 核心操作 API (高频必会)
为了便于记忆,我将数组方法按**“是否会改变原数组”**分为两类,这是面试和开发中最容易踩坑的地方。
2.1 会改变原数组的方法 (Mutating Methods)
执行这些方法后,原数组的数据或长度会被直接修改。
| 方法 | 描述 | 示例 | 时间复杂度 |
|---|---|---|---|
push() | 在末尾添加一个或多个元素,返回新长度。 | arr.push(4) | $O(1)$ |
pop() | 移除并返回末尾的最后一个元素。 | arr.pop() | $O(1)$ |
unshift() | 在头部添加一个或多个元素,返回新长度。 | arr.unshift(0) | $O(n)$ (需移动后续所有元素) |
shift() | 移除并返回头部的第一个元素。 | arr.shift() | $O(n)$ |
splice() | 万能方法。用于添加、删除、替换元素。 | arr.splice(1, 2, 'a') (从索引1删2个,插入'a') | $O(n)$ |
sort() | 对数组进行排序(默认按字符串的 Unicode 码点排)。 | arr.sort((a,b) => a - b) (按数字升序) | $O(n \log n)$ |
reverse() | 颠倒数组中元素的顺序。 | arr.reverse() | $O(n)$ |
fill() | 用静态值填充/覆盖数组的部分或全部。 | arr.fill(0) | $O(n)$ |
2.2 不改变原数组的方法 (Non-Mutating Methods)
执行这些方法会返回一个新数组或新值,原数组保持不变。(在 React/Redux 等强调不可变数据的框架中非常重要)。
| 方法 | 描述 | 示例 | 时间复杂度 |
|---|---|---|---|
concat() | 合并两个或多个数组,返回新数组。 | arr.concat([4,5]) | $O(n)$ |
slice() | 截取数组的一部分,返回新数组(浅拷贝)。 | arr.slice(1, 3) (取索引 1 到 2) | $O(n)$ |
join() | 将所有元素连接成一个字符串。 | arr.join('-') | $O(n)$ |
map() | 遍历并对每个元素执行回调,返回映射后的新数组。 | arr.map(x => x * 2) | $O(n)$ |
filter() | 遍历并过滤出满足条件的元素,返回新数组。 | arr.filter(x => x > 5) | $O(n)$ |
reduce() | 累加器,将数组所有元素计算为一个单一值。 | arr.reduce((acc, cur) => acc + cur, 0) | $O(n)$ |
forEach() | 仅遍历执行操作,没有返回值(返回 undefined)。 | arr.forEach(x => console.log(x)) | $O(n)$ |
some() | 是否有至少一个元素满足条件?返回布尔值。 | arr.some(x => x > 10) | 最优 $O(1)$, 最差 $O(n)$ |
every() | 是否所有元素都满足条件?返回布尔值。 | arr.every(x => x > 0) | 最优 $O(1)$, 最差 $O(n)$ |
find() | 查找并返回第一个满足条件的元素。 | arr.find(x => x === 3) | $O(n)$ |
findIndex() | 查找并返回第一个满足条件的索引。 | arr.findIndex(x => x === 3) | $O(n)$ |
includes() | 判断数组是否包含某个特定值(能识别 NaN)。 | arr.includes(NaN) | $O(n)$ |
indexOf() | 查找元素第一次出现的索引(找不到返回 -1)。 | arr.indexOf(3) | $O(n)$ |
3. 高阶应用技巧
3.1 数组去重
js
const arr = [1, 2, 2, 3, 3];
// 方案一:ES6 Set (最简洁,推荐)
const unique = [...new Set(arr)];
// 方案二:filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);3.2 拍平多维数组 (Flatten)
js
const arr = [1, [2, [3, 4]]];
// 方案一:ES2019 flat (参数为深度,Infinity 表示无限深)
const flatArr = arr.flat(Infinity); // [1, 2, 3, 4]
// 方案二:reduce 递归
const flatten = (ary) => ary.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? flatten(val) : val), []
);3.3 创建指定长度的初始化数组
js
// 创建一个长度为 5,全是 0 的数组
const arr1 = new Array(5).fill(0); // [0, 0, 0, 0, 0]
// 创建一个 1 到 5 的数组
const arr2 = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]4. 常见问题 (FAQ)
4.1 为什么 [1, 2, 10].sort() 的结果是 [1, 10, 2]?
- 答:JS 数组的
sort()默认将元素转换为字符串,然后比较它们的 UTF-16 码点顺序。字符串"10"的第一位"1"比"2"小,所以排在前面。 - 解决:必须传入比较函数:
arr.sort((a, b) => a - b);
4.2 for...in 和 for...of 遍历数组有什么区别?
for...in:遍历的是索引(键),且会遍历出原型链上被添加的自定义属性。极不推荐用于遍历数组。for...of:ES6 新增,专门用于遍历实现了 Iterator 接口的数据结构(如数组)。它遍历的是值,安全且高效。
4.3 forEach 循环中能使用 break 或 return 跳出循环吗?
- 答:不能。
forEach无法通过常规的break中断。return仅仅是跳出当前这一个回调函数(类似于continue)。 - 解决:如果需要提前中断循环,请使用普通的
for循环,或者some()、every()。
4.4 V8 引擎底层是如何实现 JS 数组的?
- 答:JS 数组在底层并不是纯粹的一片连续内存。V8 引擎有两种数组模式:
- 快数组 (Fast Elements):如果你的数组类型一致(如全是数字),且索引紧凑没有大跳跃,V8 会分配一块连续的线性内存,像 C 语言数组一样高效。
- 慢数组 (Dictionary Elements):如果你的数组充满空洞(如
arr[0]=1; arr[10000]=2;),或者混杂了各种对象,V8 会把它转为**哈希表(字典)**存储。这样节省了内存,但查找和插入的时间复杂度退化。
4.5 Array.prototype.slice.call(arguments) 是什么意思?
- 答:在 ES6 箭头函数和 Rest 参数
...出现之前,这是一种将类数组对象(如函数内部的arguments、DOM 节点集合NodeList)转换为真正数组的黑魔法。现在更推荐使用Array.from(arguments)或[...arguments]。