数组详解
数组是 JavaScript 中最常用的数据结构,用于有序地存放一组数据。本文从创建方式出发,系统讲解数组的增删改查、迭代方法、解构、稀疏数组等特性,并给出大量可运行示例与实际应用场景。
一、数组的创建方式
1.1 字面量
javascript
const fruits = ["苹果", "香蕉", "橙子"];
const numbers = [1, 2, 3];
const mixed = [1, "文本", true, null, { id: 1 }, [2, 3]];
const empty = [];1.2 new Array 及其陷阱
javascript
const a = new Array(3); // [empty × 3],长度 3 的空数组
const b = new Array(3, 4, 5); // [3, 4, 5],多个参数按元素处理
console.log(a.length, b); // 3 [3, 4, 5]
// 单个数字参数被当作长度,容易出错,不推荐使用1.3 Array.of 与 Array.from
javascript
// Array.of 始终把参数当作元素
console.log(Array.of(3)); // [3]
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
// Array.from 从类数组/可迭代对象创建
console.log(Array.from("abc")); // ['a','b','c']
console.log(Array.from({ length: 3 })); // [undefined, undefined, undefined]
console.log(Array.from(new Set([1, 2, 2]))); // [1, 2]
// 第二个参数是映射函数
console.log(Array.from({ length: 5 }, (_, i) => i * 2)); // [0,2,4,6,8]二、length 属性的特性
length 是数组最大的索引加 1,它是可写的:
javascript
const arr = [1, 2, 3, 4, 5];
// 缩短数组:截断尾部
arr.length = 3;
console.log(arr); // [1, 2, 3]
// 加长数组:补空槽
arr.length = 5;
console.log(arr); // [1, 2, 3, empty × 2]
// 利用 length 快速追加
arr[arr.length] = 6;
console.log(arr); // [1, 2, 3, empty, empty, 6]三、增删改查方法
3.1 首尾操作:push / pop / unshift / shift
| 方法 | 位置 | 操作 | 返回值 |
|---|---|---|---|
push(...) | 尾部 | 添加 | 新长度 |
pop() | 尾部 | 删除 | 被删元素 |
unshift(...) | 头部 | 添加 | 新长度 |
shift() | 头部 | 删除 | 被删元素 |
javascript
const stack = [];
stack.push("a", "b"); // 返回 2
console.log(stack); // ['a','b']
console.log(stack.pop()); // b
stack.unshift("x"); // 头部插入
console.log(stack); // ['x','a']
console.log(stack.shift()); // x3.2 splice:万能增删改
splice(start, deleteCount, ...items) 可删除、插入、替换:
javascript
const arr = ["a", "b", "c", "d"];
// 删除:从下标 1 开始删 2 个
const removed = arr.splice(1, 2);
console.log(arr, removed); // ['a','d'] ['b','c']
// 插入:删除 0 个,在指定位置插入
arr.splice(1, 0, "x", "y");
console.log(arr); // ['a','x','y','d']
// 替换:先删后插
arr.splice(0, 1, "A");
console.log(arr); // ['A','x','y','d']3.3 slice:提取子数组(不修改原数组)
javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.slice(1, 3)); // [2, 3],含头不含尾
console.log(arr.slice(-2)); // [4, 5]
console.log(arr.slice()); // [1,2,3,4,5],浅拷贝
console.log(arr); // 原数组不变3.4 concat:合并数组
javascript
const a = [1, 2];
const b = [3, 4];
console.log(a.concat(b, 5)); // [1, 2, 3, 4, 5]
console.log(a); // 原数组不变3.5 查找元素
| 方法 | 功能 | 返回值 |
|---|---|---|
indexOf(x) | 严格相等查找 | 下标或 -1 |
lastIndexOf(x) | 从后往前查找 | 下标或 -1 |
includes(x) | 是否包含 | 布尔值 |
find(fn) | 第一个满足条件的元素 | 元素或 undefined |
findIndex(fn) | 第一个满足条件的下标 | 下标或 -1 |
findLast(fn) | 从后往前找第一个满足的元素 | 元素或 undefined |
javascript
const nums = [5, 12, 8, 130, 44];
console.log(nums.indexOf(8)); // 2
console.log(nums.includes(130)); // true
console.log(nums.find((n) => n > 10)); // 12
console.log(nums.findIndex((n) => n > 100)); // 3
console.log(nums.findLast((n) => n > 10)); // 44四、迭代方法
4.1 forEach
javascript
["a", "b", "c"].forEach((value, index, array) => {
console.log(index, value);
});
// 0 a
// 1 b
// 2 c4.2 map:映射为新数组
javascript
const nums = [1, 2, 3];
console.log(nums.map((n) => n * n)); // [1, 4, 9]
const users = [{ name: "张三" }, { name: "李四" }];
console.log(users.map((u) => u.name)); // ['张三','李四']4.3 filter:过滤出新数组
javascript
const nums = [1, 2, 3, 4, 5, 6];
console.log(nums.filter((n) => n % 2 === 0)); // [2, 4, 6]
const products = [
{ name: "手机", price: 3000 },
{ name: "耳机", price: 200 },
{ name: "键盘", price: 500 },
];
console.log(products.filter((p) => p.price < 1000).map((p) => p.name));
// ['耳机','键盘']4.4 reduce / reduceRight:归并
javascript
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 10
// 不传初始值时以第一项为初始值
console.log(nums.reduce((acc, cur) => acc + cur)); // 10
// reduceRight 从右往左
console.log(["a", "b", "c"].reduceRight((acc, cur) => acc + cur)); // cba4.5 some / every:存在性判断
javascript
const scores = [60, 70, 80];
console.log(scores.every((s) => s >= 60)); // true
console.log(scores.some((s) => s >= 80)); // true4.6 flat / flatMap:扁平化
javascript
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]],默认只铺一层
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // 全部铺平
// flatMap = map 后再铺一层
const words = ["hello world", "hi js"];
console.log(words.flatMap((w) => w.split(" ")));
// ['hello','world','hi','js']4.7 sort:排序
javascript
const nums = [10, 5, 80, 2];
console.log(nums.sort()); // [10, 2, 5, 80],默认按字符串排序!
// 必须传入比较函数
console.log(nums.sort((a, b) => a - b)); // 升序 [2, 5, 10, 80]
console.log(nums.sort((a, b) => b - a)); // 降序 [80, 10, 5, 2]注意 sort 会原地修改数组,需要保留原数组时应先拷贝。
4.8 reverse 与 join
javascript
const arr = [1, 2, 3];
console.log(arr.reverse()); // [3, 2, 1],原地反转
console.log(arr.join("-")); // "3-2-1"五、扩展运算符与解构
5.1 扩展运算符
javascript
// 合并数组
const a = [1, 2], b = [3, 4];
console.log([...a, ...b]); // [1, 2, 3, 4]
// 拷贝数组(浅拷贝)
const copy = [...a];
copy.push(99);
console.log(a); // [1, 2],不受影响
// 拼接与插入
const arr = [1, 5];
console.log([...arr.slice(0, 1), 2, 3, 4, ...arr.slice(1)]); // [1,2,3,4,5]
// 函数调用传参
const max = Math.max(...[3, 9, 5]);
console.log(max); // 95.2 数组解构
javascript
const [first, second] = [1, 2, 3];
console.log(first, second); // 1 2
const [head, ...rest] = [1, 2, 3, 4];
console.log(head, rest); // 1 [2, 3, 4]
const [x = 10] = [];
console.log(x); // 10,默认值
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1六、多维数组
javascript
// 二维数组:矩阵
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
console.log(matrix[1][2]); // 6
// 遍历二维数组
for (const row of matrix) {
for (const cell of row) {
process.stdout.write(cell + " ");
}
}
// 1 2 3 4 5 6 7 8 9
// 二维数组求和
const total = matrix.flat().reduce((acc, n) => acc + n, 0);
console.log(total); // 45七、稀疏数组与空槽
创建数组时跳过下标,或用 new Array(n),都会产生空槽(hole)。空槽不是 undefined,某些方法会跳过它:
javascript
const sparse = [1, , 3]; // 下标 1 是空槽
console.log(sparse.length); // 3
console.log(sparse[1]); // undefined(访问空槽得到 undefined)
// forEach / map / filter 会跳过空槽
sparse.forEach((v) => console.log(v)); // 只输出 1 和 3
console.log(sparse.map((v) => v * 2)); // [2, empty, 6]
// Array.from 与扩展运算符会填充为 undefined
console.log(Array.from(sparse)); // [1, undefined, 3]
console.log([...sparse]); // [1, undefined, 3]Array(3) 与 [undefined, undefined, undefined] 并不等价,前者是空槽数组,使用时需留意这一差异。
八、类数组对象与 Array.from / Array.isArray
类数组对象具有 length 属性和数字下标,但不是真正的数组:
javascript
// arguments 是典型类数组
function sum() {
return Array.from(arguments).reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3)); // 6
// DOM 集合(NodeList、HTMLCollection)也是类数组
// const nodes = document.querySelectorAll("div");
// const arr = Array.from(nodes);
// Array.isArray 判断是否真数组
console.log(Array.isArray([1, 2])); // true
console.log(Array.isArray({ length: 2 })); // false
console.log(Array.isArray("abc")); // false
console.log(Array.isArray(arguments)); // true九、数组去重
javascript
// 方法一:Set(推荐,简单高效)
function unique(arr) {
return [...new Set(arr)];
}
console.log(unique([1, 2, 2, 3, 3, 3])); // [1, 2, 3]
// 方法二:filter + indexOf
function unique2(arr) {
return arr.filter((v, i) => arr.indexOf(v) === i);
}
console.log(unique2([1, 2, 2, 3])); // [1, 2, 3]
// 方法三:reduce + includes
function unique3(arr) {
return arr.reduce((acc, v) => (acc.includes(v) ? acc : [...acc, v]), []);
}
console.log(unique3([1, 2, 2, 3])); // [1, 2, 3]
// 对象数组按某个字段去重
const users = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 1, name: "张三" },
];
const byId = [...new Map(users.map((u) => [u.id, u])).values()];
console.log(byId); // [{id:1,name:'张三'},{id:2,name:'李四'}]十、排序进阶
javascript
// 按字符串长度排序
const words = ["banana", "apple", "kiwi"];
console.log(words.sort((a, b) => a.length - b.length)); // ['kiwi','apple','banana']
// 按对象属性排序
const products = [
{ name: "手机", price: 3000 },
{ name: "耳机", price: 200 },
{ name: "键盘", price: 500 },
];
console.log(products.sort((a, b) => a.price - b.price).map((p) => p.name));
// ['耳机','键盘','手机']
// 多字段排序:先按价格升序,再按名称降序
const data = [
{ price: 100, name: "a" },
{ price: 50, name: "b" },
{ price: 100, name: "c" },
];
data.sort((x, y) => x.price - y.price || y.name.localeCompare(x.name));
console.log(data);
// [{price:50,name:'b'},{price:100,name:'c'},{price:100,name:'a'}]
// 排序稳定性:相同 key 保持原相对顺序
const sorted = [2, 1, 2].map((v, i) => ({ v, i })).sort((a, b) => a.v - b.v);
console.log(sorted.map((o) => o.i)); // [1, 0, 2]十一、reduce 进阶用法
11.1 统计频次
javascript
const fruits = ["苹果", "香蕉", "苹果", "橙子", "苹果"];
const count = fruits.reduce((acc, f) => {
acc[f] = (acc[f] || 0) + 1;
return acc;
}, {});
console.log(count); // { 苹果: 3, 香蕉: 1, 橙子: 1 }11.2 分组
javascript
const students = [
{ name: "张三", score: 88 },
{ name: "李四", score: 55 },
{ name: "王五", score: 72 },
];
const groups = students.reduce((acc, s) => {
const key = s.score >= 60 ? "及格" : "不及格";
(acc[key] ||= []).push(s.name);
return acc;
}, {});
console.log(groups); // { 及格: ['张三','王五'], 不及格: ['李四'] }11.3 扁平化任意深度
javascript
function flatten(arr) {
return arr.reduce(
(acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur),
[]
);
}
console.log(flatten([1, [2, [3, [4]]]])); // [1, 2, 3, 4]11.4 对象数组求最大值与平均值
javascript
const sales = [
{ month: "1月", amount: 1200 },
{ month: "2月", amount: 900 },
{ month: "3月", amount: 1500 },
];
const max = sales.reduce((acc, s) => (s.amount > acc.amount ? s : acc));
console.log(max); // { month: '3月', amount: 1500 }
const avg = sales.reduce((acc, s, _, arr) => acc + s.amount / arr.length, 0);
console.log(avg); // 1200十二、综合实战
javascript
// 任务:对成绩数组完成过滤、映射、排序与统计
const scores = [62, 88, 45, 90, 71, 59, 96];
// 1. 过滤出及格成绩并升序排序
const passed = scores.filter((s) => s >= 60).sort((a, b) => a - b);
console.log(passed); // [62, 71, 88, 90, 96]
// 2. 映射为评级文本
const levels = scores.map((s) =>
s >= 90 ? "优秀" : s >= 70 ? "良好" : s >= 60 ? "及格" : "不及格"
);
console.log(levels); // ['及格','良好','不及格','优秀','良好','不及格','优秀']
// 3. 统计各等级人数
const stat = levels.reduce((acc, l) => {
acc[l] = (acc[l] || 0) + 1;
return acc;
}, {});
console.log(stat); // { 及格: 1, 良好: 2, 不及格: 2, 优秀: 2 }
// 4. 批量创建数据:Array.from + 映射
const weekdays = Array.from({ length: 7 }, (_, i) => `第${i + 1}天`);
console.log(weekdays); // ['第1天',...,'第7天']数组方法的核心是"返回新数组不修改原数组"(map/filter/slice/concat)与"原地修改"(sort/reverse/splice/push 等)两条主线,使用时先分清二者,再借助链式调用写出清晰的数据处理管道。