数组
基于 ECMAScript 2025 规范 Section 23.1
什么是 Array?
Array(数组) 是 JavaScript 中用于存储有序列表的数据结构。
// 创建一个数组
const fruits = ["apple", "banana", "orange"];
// 访问元素(从 0 开始)
fruits[0]; // "apple"
fruits[1]; // "banana"
fruits[2]; // "orange"
// 获取长度
fruits.length; // 3数组的本质
数组本质上是特殊的对象,但有以下区别:
| 特性 | 普通对象 | 数组 |
|---|---|---|
| 键 | 任意字符串 | 整数索引(0, 1, 2...) |
| 长度 | 无自动管理 | 自动维护 length |
| 迭代 | 不保证顺序 | 按插入顺序 |
// 数组其实是对象
const arr = [1, 2, 3];
typeof arr; // "object"
// 但如果你用非整数作为 key,它就变成"普通对象属性"了
arr[5.5] = "hello";
arr.length; // 3 —— 长度不变!
arr; // [1, 2, 3, empty × 2, 5.5: "hello"]一、数组的创建方式
1.1 数组字面量(最常用)
const arr1 = [1, 2, 3];
const arr2 = []; // 空数组
const arr3 = [1, , 3]; // 有空槽 [1, <empty>, 3]1.2 构造函数 Array()
// 多个参数 = 元素
new Array(1, 2, 3); // [1, 2, 3]
// 单个数字参数 = 长度(创建稀疏数组)
new Array(5); // [empty × 5] —— 5 个空槽⚠️ 注意:单参数容易产生歧义,new Array(3) 到底是 3 个元素还是长度 3?
1.3 Array.of() —— 解决歧义
// ES6 新增,无论几个参数都当作元素
Array.of(5); // [5]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of(); // []1.4 Array.from() —— 万能转换
// 从类数组对象转换
Array.from({ 0: "a", 1: "b", length: 2 }); // ["a", "b"]
// 从可迭代对象转换
Array.from("hello"); // ["h", "e", "l", "l", "o"]
// 还能顺便做映射(第二个参数是 map 函数)
Array.from({ length: 3 }, (_, i) => i * i); // [0, 1, 4]1.5 展开运算符 ...
// 克隆数组
const arr = [1, 2, 3];
[...arr]; // [1, 2, 3]
// 合并数组
const a = [1, 2];
const b = [3, 4];
[...a, ...b]; // [1, 2, 3, 4]
// 去重
[...new Set([1, 2, 2, 3])]; // [1, 2, 3]1.6 Array.fill() —— 填充数组
// 创建固定值的数组
new Array(4).fill(0); // [0, 0, 0, 0]
// fill 还能指定起始位置
[1, 2, 3, 4].fill("x", 1, 3); // [1, "x", "x", 4]二、数组的"长度"特性
2.1 length 的自动更新
const arr = [1, 2, 3];
arr.length; // 3
// 添加元素会自动延长
arr[5] = 6;
arr.length; // 6 —— 自动更新!
arr; // [1, 2, 3, empty × 2, 6]2.2 length 可以手动修改
const arr = [1, 2, 3, 4, 5];
// 缩短数组(删除末尾元素)
arr.length = 3;
arr; // [1, 2, 3]
// 清空数组
arr.length = 0;
arr; // []
// 拉长数组(产生空槽)
arr.length = 5;
arr; // [empty × 5]2.3 稀疏数组
空槽(empty item) 和 undefined 是不同的!
const arr1 = [1, undefined, 3]; // 有 3 个元素,第二个是 undefined
const arr2 = [1, , 3]; // 稀疏数组,中间是"空槽"
arr1.length; // 3
arr2.length; // 3
arr1[1]; // undefined
arr2[1]; // undefined —— 读取时也是 undefined,但"空"和"有值"不同空槽的创建方式:
Array(3); // [empty × 3]
[1, , 3]; // [1, <empty>, 3]
const a = [1, 2];
a[5] = 6; // [1, 2, <3 empty items>, 6]
a.length = 10; // [1, 2, <8 empty items>]
delete a[1]; // [<empty>, 2, <8 empty items>]三、添加 / 删除元素(改变原数组)
3.1 push() —— 末尾添加
const arr = [1, 2, 3];
arr.push(4, 5); // 返回新长度 5
arr; // [1, 2, 3, 4, 5]3.2 pop() —— 末尾删除
const arr = [1, 2, 3];
arr.pop(); // 返回删除的元素 3
arr; // [1, 2]3.3 unshift() —— 开头添加
const arr = [1, 2, 3];
arr.unshift(0); // 返回新长度 4
arr; // [0, 1, 2, 3]3.4 shift() —— 开头删除
const arr = [1, 2, 3];
arr.shift(); // 返回删除的元素 1
arr; // [2, 3]3.5 splice() —— 万能操作(增删改)
const arr = [1, 2, 3, 4, 5];
// 删除:splice(起始索引, 删除个数)
arr.splice(1, 2); // 返回删除的 [2, 3]
arr; // [1, 4, 5]
// 插入:splice(起始索引, 0, 新元素...)
arr.splice(1, 0, 2, 3);
arr; // [1, 2, 3, 4, 5]
// 替换:splice(起始索引, 删除个数, 新元素...)
arr.splice(1, 2, "a", "b");
arr; // [1, "a", "b", 4, 5]
// 清空全部:splice(0)
arr.splice(0); // 返回所有元素,arr 变为 []四、不改变原数组的方法
4.1 slice() —— 切片
const arr = [1, 2, 3, 4, 5];
// slice(起始, 结束) —— 不包括结束位置
arr.slice(1, 4); // [2, 3, 4]
// 负数索引
arr.slice(-3, -1); // [3, 4]
// 省略结束位置 = 到末尾
arr.slice(2); // [3, 4, 5]
// 复制整个数组
arr.slice(); // [1, 2, 3, 4, 5]4.2 concat() —— 合并
const a = [1, 2];
const b = [3, 4];
a.concat(b, 5, 6); // [1, 2, 3, 4, 5, 6]
a; // [1, 2] —— 原数组不变4.3 toReversed() / toSorted() / toSpliced() / with()
ES2023 新增的不改变原数组的版本:
const arr = [3, 1, 2];
arr.toReversed(); // [1, 2, 3] —— 翻转
arr; // [3, 1, 2] —— 原数组不变
arr.toSorted(); // [1, 2, 3] —— 排序
arr; // [3, 1, 2] —— 原数组不变
arr.toSpliced(1, 1, "x"); // [3, "x", 2] —— 替换
arr; // [3, 1, 2]
arr.with(0, "y"); // ["y", 1, 2] —— 修改指定位置
arr; // [3, 1, 2]五、查找元素
5.1 indexOf() / lastIndexOf()
const arr = [1, 2, 3, 2, 1];
arr.indexOf(2); // 1 —— 首次出现的位置
arr.lastIndexOf(2); // 3 —— 最后一次出现的位置
arr.indexOf(99); // -1 —— 没找到5.2 includes() —— ES2016
const arr = [1, 2, 3];
arr.includes(2); // true
arr.includes(99); // false
// 注意:includes 能正确处理 NaN
[NaN].indexOf(NaN); // -1 (错误!应该找到)
[NaN].includes(NaN); // true (正确)5.3 find() / findIndex() / findLast() / findLastIndex()
const users = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "王五" }
];
// find: 返回找到的元素
users.find(u => u.id === 2); // { id: 2, name: "李四" }
users.find(u => u.id === 99); // undefined
// findIndex: 返回找到的索引
users.findIndex(u => u.id === 2); // 1
// findLast: 从右往左找
users.findLast(u => u.id < 3); // { id: 2 }
// findLastIndex: 从右往左找索引
users.findLastIndex(u => u.id < 3); // 1六、遍历数组
6.1 for...of
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item); // 1, 2, 3
}
// 还能拿到索引
for (const [index, item] of arr.entries()) {
console.log(index, item); // 0 1, 1 2, 2 3
}6.2 forEach()
const arr = [1, 2, 3];
arr.forEach((item, index, array) => {
console.log(item, index);
});
// forEach 的特点:
// - 返回 undefined(不是链式调用)
// - 不能 break / continue(会遍历全部)
// - 空槽会被跳过
[1, , 3].forEach(x => console.log(x)); // 1, 3(跳过空槽)6.3 map() —— 映射
const arr = [1, 2, 3];
// 对每个元素执行函数,返回新数组
arr.map(x => x * 2); // [2, 4, 6]
// 还能拿到索引
arr.map((x, i) => x + i); // [1, 3, 5]6.4 filter() —— 过滤
const arr = [1, 2, 3, 4, 5];
// 返回满足条件的元素组成的新数组
arr.filter(x => x > 3); // [4, 5]
arr.filter(x => x % 2 === 0); // [2, 4]6.5 every() / some()
const arr = [1, 2, 3, 4];
// every: 所有都满足?
arr.every(x => x > 0); // true
arr.every(x => x > 2); // false
// some: 至少有一个满足?
arr.some(x => x > 3); // true
arr.some(x => x > 10); // false6.6 reduce() / reduceRight()
const arr = [1, 2, 3, 4];
// 累加
arr.reduce((sum, x) => sum + x, 0); // 10
// reduceRight 从右往左
arr.reduceRight((str, x) => str + x, ""); // "4321"reduce 的参数:
javascript
arr.reduce((累计值, 当前元素, 当前索引, 原数组) => {}, 初始值);
七、数组排序
7.1 sort() —— 原位排序
const arr = [1, 15, 2, 10];
// 默认按字符串排序(会出错!)
arr.sort(); // [1, 10, 15, 2]
// 正确用法:传入比较函数
arr.sort((a, b) => a - b); // [1, 2, 10, 15] 升序
arr.sort((a, b) => b - a); // [15, 10, 2, 1] 降序比较函数规则:
- 返回负数 → a 在前
- 返回 0 → 相等
- 返回正数 → b 在前
7.2 reverse() —— 翻转
const arr = [1, 2, 3];
arr.reverse(); // [3, 2, 1]八、转换方法
8.1 join() —— 数组转字符串
const arr = [1, 2, 3];
arr.join(); // "1,2,3"
arr.join(""); // "123"
arr.join("-"); // "1-2-3"
// 稀疏数组的空槽会变成空字符串
[1, , 3].join("-"); // "1--3"8.2 split() —— 字符串转数组(String 方法)
"hello world".split(" "); // ["hello", "world"]
"hello".split(""); // ["h", "e", "l", "l", "o"]
// 还能限制长度
"hello world".split(" ", 2); // ["hello", "world"]8.3 toReversed() / toSorted() / toSpliced() / with()
见 4.3 节。
九、flat() / flatMap() —— 扁平化
9.1 flat() —— 展平数组
[1, [2, [3, [4]]]].flat(); // [1, 2, [3, [4]]] —— 默认展平 1 层
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]] —— 展平 2 层
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4] —— 全部展平9.2 flatMap() —— 先 map 后 flat
const arr = [1, 2, 3];
// 相当于
arr.map(x => [x, x * 2]).flat();
// [1, 2, 2, 4, 3, 6]
// flatMap 更简洁
arr.flatMap(x => [x, x * 2]);
// [1, 2, 2, 4, 3, 6]十、ES2015+ 迭代器方法
10.1 keys() / values() / entries()
const arr = ["a", "b", "c"];
[...arr.keys()]; // [0, 1, 2]
[...arr.values()]; // ["a", "b", "c"]
[...arr.entries()]; // [[0, "a"], [1, "b"], [2, "c"]]
// 配合 for...of
for (const [index, value] of arr.entries()) {
console.log(index, value);
}十一、Array.prototype[Symbol.iterator]
所有数组迭代器方法(for...of、spread、Array.from)底层都调用这个方法。
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: 3, done: false }
iterator.next(); // { value: undefined, done: true }十二、数组的空槽 vs undefined
| 特点 | 空槽 (empty) | undefined |
|---|---|---|
in 运算符 | false | true |
Object.keys() | 不包含 | 包含 |
for...of | 跳过 | 遍历到 |
forEach | 跳过 | 遍历到 |
map/filter | 跳过 | 处理 |
const arr = [1, , 3];
1 in arr; // false —— 空槽位置没有属性
const arr2 = [1, undefined, 3];
1 in arr2; // true
Object.keys(arr); // ["0", "2"]
Object.keys(arr2); // ["0", "1", "2"]十三、常见误区
13.1 Array(3) vs Array.of(3)
Array(3); // [empty × 3] —— 长度为 3 的空数组
Array.of(3); // [3] —— 只有一个元素 313.2 splice vs slice
- splice → 改变原数组(删除/插入/替换)
- slice → 不改变原数组(返回副本)
13.3 sort 的坑
// 常见错误
[1, 2, 10].sort(); // [1, 10, 2] —— 按字符串排序!
// 正确
[1, 2, 10].sort((a, b) => a - b); // [1, 2, 10]13.4 forEach 不能链式调用
// 错误
[1, 2, 3].forEach(x => console.log(x)).map(x => x); // TypeError
// 正确 - 先收集结果再操作
const result = [];
[1, 2, 3].forEach(x => result.push(x));十四、方法速查表
| 方法 | 返回值 | 改变原数组 | 说明 |
|---|---|---|---|
| push() | 新长度 | ✅ | 末尾添加 |
| pop() | 删除的元素 | ✅ | 末尾删除 |
| unshift() | 新长度 | ✅ | 开头添加 |
| shift() | 删除的元素 | ✅ | 开头删除 |
| splice() | 删除的元素 | ✅ | 增删改 |
| slice() | 新数组 | ❌ | 切片 |
| concat() | 新数组 | ❌ | 合并 |
| map() | 新数组 | ❌ | 映射 |
| filter() | 新数组 | ❌ | 过滤 |
| find() | 元素/undefined | ❌ | 查找 |
| findIndex() | 索引/-1 | ❌ | 查找索引 |
| includes() | boolean | ❌ | 是否包含 |
| indexOf() | 索引/-1 | ❌ | 查找位置 |
| every() | boolean | ❌ | 全满足? |
| some() | boolean | ❌ | 有一个满足? |
| reduce() | 累计结果 | ❌ | 归约 |
| sort() | 数组 | ✅ | 排序 |
| reverse() | 数组 | ✅ | 翻转 |
| join() | 字符串 | ❌ | 转字符串 |
| flat() | 新数组 | ❌ | 扁平化 |
| toReversed() | 新数组 | ❌ | 翻转(不改原) |
| toSorted() | 新数组 | ❌ | 排序(不改原) |
| toSpliced() | 新数组 | ❌ | 增删改(不改原) |
| with() | 新数组 | ❌ | 修改指定位置 |
参考
- ECMAScript 2025 Language Specification Section 23.1
- Array 对象及其方法