Appearance
13|JavaScript 数组与常用方法
数组是 JavaScript 中最常用的数据结构之一:服务器列表、日志条目、接口返回的数据——这些在代码中都是数组。掌握数组的创建、访问、修改和高阶方法,能大幅减少手写循环的代码量。
一、数组创建与访问
字面量创建
js
const empty = [];
const services = ["nginx", "redis", "mysql"];
const mixed = ["text", 42, true, { a: 1 }]; // 可以混存不同类型(不推荐)Array 构造函数
js
const arr1 = new Array(3); // [empty × 3],长度为 3 的空槽数组
const arr2 = new Array(1, 2, 3); // [1, 2, 3]
// 字面量更简洁,推荐用 []
const arr3 = [1, 2, 3];访问元素
js
const services = ["nginx", "redis", "mysql"];
console.log(services[0]); // "nginx",索引从 0 开始
console.log(services[2]); // "mysql"
console.log(services[5]); // undefined,越界不报错
console.log(services.length); // 3
// 取最后一个元素
console.log(services[services.length - 1]); // "mysql"
console.log(services.at(-1)); // "mysql"(ES2022,负数索引从末尾数)修改元素
js
services[1] = "postgres"; // 修改索引 1 的元素
services[5] = "elasticsearch"; // 索引 3、4 变为空槽,length 变为 6直接给大于当前长度的索引赋值,会在中间创建空槽(empty slots),可能导致遍历行为异常。
二、增删元素
尾部操作
js
const arr = ["a", "b"];
arr.push("c"); // 末尾添加,arr 变为 ["a", "b", "c"],返回新长度 3
arr.pop(); // 删除末尾,arr 变为 ["a", "b"],返回被删元素 "c"头部操作
js
arr.unshift("z"); // 头部添加,arr 变为 ["z", "a", "b"],返回新长度 3
arr.shift(); // 删除头部,arr 变为 ["a", "b"],返回被删元素 "z"push/pop 操作效率高(O(1)),unshift/shift 需要移动所有现有元素(O(n))。频繁在头部增删时,考虑用其他数据结构。
splice
在任意位置增删改:
js
const arr = ["a", "b", "c", "d"];
// 从索引 1 开始,删除 2 个元素,并插入 "x"、"y"
arr.splice(1, 2, "x", "y"); // 返回 ["b", "c"]
console.log(arr); // ["a", "x", "y", "d"]| 参数 | 含义 |
|---|---|
| 第 1 个 | 起始索引 |
| 第 2 个 | 删除数量(0 表示不删除) |
| 第 3 个及以后 | 要插入的元素 |
三、查找与判断
indexOf / includes
js
const arr = ["nginx", "redis", "mysql"];
arr.indexOf("redis"); // 1,找到返回索引,找不到返回 -1
arr.indexOf("postgres"); // -1
arr.includes("redis"); // true
arr.includes("postgres"); // false这两个方法使用 === 做比较。对于对象数组,判断的是引用而非内容:
js
const servers = [{ name: "web-01" }, { name: "web-02" }];
servers.includes({ name: "web-01" }); // false(不是同一个对象引用)find / findIndex
按条件查找对象:
js
const server = servers.find((s) => s.name === "web-01");
// { name: "web-01" }
const index = servers.findIndex((s) => s.name === "web-01");
// 0四、排序与反转
sort
默认按字符串 Unicode 排序:
js
const nums = [10, 2, 30, 1];
nums.sort();
console.log(nums); // [1, 10, 2, 30] —— "10" 的 Unicode 小于 "2"数字排序需要传入比较函数:
js
nums.sort((a, b) => a - b); // 升序 [1, 2, 10, 30]
nums.sort((a, b) => b - a); // 降序 [30, 10, 2, 1]比较函数返回值:
- 负数:
a排在b前面 - 正数:
a排在b后面 - 0:顺序不变
reverse
js
const arr = ["a", "b", "c"];
arr.reverse();
console.log(arr); // ["c", "b", "a"]sort 和 reverse 都会修改原数组。需要保留原数组时,先复制:
js
const sorted = [...arr].sort((a, b) => a - b);五、切片与拼接
slice
返回新数组,不修改原数组:
js
const arr = ["a", "b", "c", "d"];
arr.slice(1, 3); // ["b", "c"]:从索引 1 到 3(不含)
arr.slice(2); // ["c", "d"]:从索引 2 到末尾
arr.slice(-2); // ["c", "d"]:最后两个
arr.slice(); // ["a", "b", "c", "d"]:浅拷贝整个数组concat
合并数组:
js
const a = ["nginx"];
const b = ["redis", "mysql"];
const c = a.concat(b); // ["nginx", "redis", "mysql"]也可以用展开运算符:
js
const c = [...a, ...b];六、高阶方法详解
map
对每个元素做转换,返回等长的新数组:
js
const ports = [80, 443, 3306];
const labels = ports.map((p) => `端口 ${p}`);
// ["端口 80", "端口 443", "端口 3306"]map 适合"一一转换"的场景。如果只是想遍历执行副作用,用 forEach;如果想筛选,用 filter。
filter
返回满足条件的新数组:
js
const services = [
{ name: "nginx", status: "running" },
{ name: "redis", status: "stopped" },
{ name: "mysql", status: "running" },
];
const running = services.filter((s) => s.status === "running");
// [{ name: "nginx", ... }, { name: "mysql", ... }]reduce
把数组归约为单个值:
js
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
// 10
// 对象分组
const grouped = services.reduce((acc, s) => {
const key = s.status;
if (!acc[key]) acc[key] = [];
acc[key].push(s);
return acc;
}, {});
// { running: [...], stopped: [...] }reduce 的第一个参数是回调函数(接收累加器和当前元素),第二个参数是初始值。初始值不能省略,否则数组为空时会报错。
七、扁平化与去重
flat
js
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]]:默认展平一层
nested.flat(2); // [1, 2, 3, 4, 5, 6]:展平两层Set 去重
js
const nums = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(nums)]; // [1, 2, 3]Set 是值不重复集合。先转成 Set 去重,再展开成数组。
八、常见错误
用 typeof 判断数组
js
// 错误
if (typeof arr === "object") { // 对象也满足
// 正确
if (Array.isArray(arr)) {sort 默认行为不符合预期
js
const nums = [10, 2, 1];
nums.sort();
console.log(nums); // [1, 10, 2],不是 [1, 2, 10]
// 正确
nums.sort((a, b) => a - b);在遍历中修改原数组
js
// 错误
const arr = [1, 2, 3];
arr.forEach((item, index) => {
arr.splice(index, 1); // 索引错位,结果不符合预期
});
// 正确:倒序遍历
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] < 2) {
arr.splice(i, 1);
}
}map 的返回值被忽略
js
// 错误:没有使用 map 的返回值,且修改了原数组
services.map((s) => {
s.status = "running"; // 副作用
});
// 正确:返回新数组,不修改原数组
const updated = services.map((s) => ({
...s,
status: "running",
}));