JavaScript 数组常用方法
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
数组方法很多,但日常用到的不超过十来个。把它们按是否修改原数组分成两类,就能避免大部分「数据被意外改掉」的问题。
两类方法:改原数组 vs 返回新数组
| 修改原数组 | 返回新结果(原数组不变) |
|---|---|
push pop shift unshift |
map filter slice concat |
splice sort reverse |
reduce find some every |
fill copyWithin |
flat flatMap toSorted toReversed |
记不住就记这一条:凡是你没明确要求「原地修改」的,优先用返回新数组的那组。
map:逐个转换
const nums = [1, 2, 3];
const doubled = nums.map((n) => n * 2);
console.log(doubled); // [2, 4, 6]
console.log(nums); // [1, 2, 3] 原数组不变
map 的回调返回值组成新数组,长度与原数组一致。想同时过滤请用 filter 或 flatMap,不要用 map 返回 undefined 再想办法剔除。
filter:保留满足条件的
const nums = [1, 2, 3, 4, 5];
const evens = nums.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4]
reduce:聚合成一个值
const nums = [1, 2, 3, 4];
const total = nums.reduce((sum, n) => sum + n, 0);
console.log(total); // 10
第二个参数 0 是累加器初值,绝大多数情况下都要给。不给初值时,第一次迭代用数组第一个元素当累加器,空数组会直接抛 TypeError。
分组统计是 reduce 的典型场景:
const words = ["apple", "ant", "banana", "berry"];
const groups = words.reduce((acc, word) => {
const key = word[0];
acc[key] ??= [];
acc[key].push(word);
return acc;
}, {});
console.log(groups);
// { a: ["apple", "ant"], b: ["banana", "berry"] }
求和、计数、分组、扁平化都能用 reduce,但「过滤 + 转换」这类组合写成 filter().map() 更好读。
find / findIndex / some / every
const users = [
{ id: 1, name: "ann", active: true },
{ id: 2, name: "bob", active: false },
];
users.find((u) => u.id === 2); // { id: 2, ... } 找不到返回 undefined
users.findIndex((u) => !u.active); // 1,找不到返回 -1
users.some((u) => u.active); // true,至少一个满足
users.every((u) => u.active); // false,不是全部满足
some 与 every 会短路:结果确定后立即停止遍历,比 filter().length > 0 更高效也更直白。
includes / indexOf
["a", "b"].includes("b"); // true
["a", "b"].indexOf("b"); // 1,不存在返回 -1
只判断存在用 includes;需要位置用 indexOf。注意两者都用 === 比较,对象比较的是引用:
const a = { id: 1 };
[a].includes({ id: 1 }); // false,两个不同的对象
[a].includes(a); // true
slice 与 splice(名字很像,行为完全不同)
const nums = [1, 2, 3, 4, 5];
nums.slice(1, 3); // [2, 3],返回新数组,nums 不变
nums.splice(1, 2); // 返回被删掉的 [2, 3],nums 变成 [1, 4, 5]
slice(start, end) 是「取一段」(含头不含尾)、不改原数组;splice(start, deleteCount, ...items) 是「在原数组上删除/插入」。
sort 的默认行为(坑)
const nums = [10, 9, 1, 20];
nums.sort();
console.log(nums); // [1, 10, 20, 9]
sort() 默认把元素转成字符串按字典序排,所以 9 排在 20 后面。数值排序必须传比较函数:
nums.sort((a, b) => a - b); // 升序 [1, 9, 10, 20]
nums.sort((a, b) => b - a); // 降序
另外 sort() 是原地排序。想保留原数组,用 toSorted()(ES2023,Node 20+ 支持):
const sorted = nums.toSorted((a, b) => a - b); // nums 不变
flat 与 flatMap
[[1, 2], [3, [4]]].flat(); // [1, 2, 3, [4]] 只展平一层
[[1, 2], [3, [4]]].flat(2); // [1, 2, 3, 4] 展平两层
[[1, 2], [3, [4]]].flat(Infinity); // 全部展平
flatMap 等于「map 之后展平一层」,适合「一项变多项、或一项变零项」:
const lines = ["a,b", "c"];
const cells = lines.flatMap((line) => line.split(","));
console.log(cells); // ["a", "b", "c"]
链式调用
const orders = [
{ amount: 120, paid: true },
{ amount: 80, paid: false },
{ amount: 200, paid: true },
];
const total = orders
.filter((o) => o.paid)
.map((o) => o.amount)
.reduce((sum, n) => sum + n, 0);
console.log(total); // 320
链太长(超过 3 段)就拆成中间变量,可读性和调试都更好。
常见错误与注意事项
sort()不传比较函数:按字符串排序,数字顺序错乱。reduce不传初值 + 空数组:抛TypeError,始终给初值。- 混淆
slice与splice:一个不改原数组,一个改。 - 在
forEach里push到另一个数组:这是map的活,直接map更清晰。 - 遍历时用
splice删除元素:下标错位漏项,用filter生成新数组。 - 用
filter(x).length > 0判断存在:用some(),会短路。
小结
map/filter/reduce/find/slice不改原数组;splice/sort/reverse/push会改。- 数值排序必须传比较函数;要保留原数组用
toSorted()。 reduce一定传初值。- 判断存在用
some(),找元素用find()。