JavaScript 数组常用方法

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Node.js 22 LTS

数组方法很多,但日常用到的不超过十来个。把它们按是否修改原数组分成两类,就能避免大部分「数据被意外改掉」的问题。

两类方法:改原数组 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 段)就拆成中间变量,可读性和调试都更好。

常见错误与注意事项

  1. sort() 不传比较函数:按字符串排序,数字顺序错乱。
  2. reduce 不传初值 + 空数组:抛 TypeError,始终给初值。
  3. 混淆 slice 与 splice:一个不改原数组,一个改。
  4. 在 forEach 里 push 到另一个数组:这是 map 的活,直接 map 更清晰。
  5. 遍历时用 splice 删除元素:下标错位漏项,用 filter 生成新数组。
  6. 用 filter(x).length > 0 判断存在:用 some(),会短路。

小结

  • map / filter / reduce / find / slice 不改原数组;splice / sort / reverse / push 会改。
  • 数值排序必须传比较函数;要保留原数组用 toSorted()。
  • reduce 一定传初值。
  • 判断存在用 some(),找元素用 find()。

下一步