跳到主要内容

JavaScript 数组方法全景:会查、会改、会遍历、会选择

数组几乎是前端里使用频率最高的数据结构之一。你每天都会遇到这些需求:

  • 把接口返回的数据过滤一遍
  • 找到某个满足条件的元素
  • 统计总价、总数、词频
  • 向数组头尾插入或删除数据
  • 排序、去重、扁平化、转字符串

数组方法很多,但真正要掌握的不是“死记 API 名称”,而是三件事:

  1. 这个方法会不会修改原数组
  2. 这个方法返回什么
  3. 这个方法更适合“查”“改”“变换”还是“聚合”

这篇文档的目标是:用一张方法地图 + 一组高频示例,把常用数组方法一次讲清楚。


1. 先建立总图:数组方法最重要的分类

很多同学一上来就背方法名,结果越背越乱。更高效的记法是先分成两大类:

  • 修改原数组
  • 不修改原数组

1.1 会修改原数组的方法

方法作用返回值
push()末尾追加新长度
pop()删除末尾元素被删除的元素
shift()删除开头元素被删除的元素
unshift()开头插入新长度
splice()任意位置插入 / 删除 / 替换被删除元素组成的新数组
sort()排序原数组本身
reverse()反转原数组本身
fill()用固定值填充区间原数组本身
copyWithin()把一段内容复制到另一位置原数组本身

1.2 不修改原数组的方法

方法作用返回值
slice()截取一段新数组
concat()合并数组新数组
map()映射转换新数组
filter()条件过滤新数组
flat()扁平化新数组
flatMap()映射后再拍平一层新数组
find()找到第一个满足条件的元素元素或 undefined
findIndex()找到第一个满足条件元素的下标下标或 -1
includes()判断是否包含某值boolean
some()是否至少一个满足条件boolean
every()是否全部满足条件boolean
reduce()聚合成一个结果累加结果
join()拼接字符串字符串
toSorted()排序,但不改原数组新数组
toReversed()反转,但不改原数组新数组
toSpliced()splice 的不变版本新数组
with()替换某一项,但不改原数组新数组
先记住这一句

会不会改原数组,是数组题里最容易出 bug 的分界线。


2. 创建与识别数组

2.1 Array.isArray():判断是不是数组

console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray('abc')); // false
console.log(Array.isArray({ length: 2 })); // false

为什么不用 typeof

因为:

typeof []; // "object"

所以判断数组最稳妥的方式就是 Array.isArray()

2.2 Array.from():把“类数组 / 可迭代对象”转成真正数组

const set = new Set([1, 2, 3]);
const arr1 = Array.from(set);

const arr2 = Array.from({ length: 5 }, (_, i) => i * 2);

console.log(arr1); // [1, 2, 3]
console.log(arr2); // [0, 2, 4, 6, 8]

常见用途:

  • Set 转数组
  • NodeList 转数组
  • 生成固定长度数组

2.3 Array.of():按参数原样创建数组

console.log(Array.of(3)); // [3]
console.log(Array(3)); // [ <3 empty items> ]

这一点很重要:Array(3) 表示“长度为 3 的空数组”,而不是 [3]


3. 读取、查找与判断

3.1 at():按位置读取,支持负索引

const arr = ['a', 'b', 'c', 'd'];

console.log(arr.at(0)); // 'a'
console.log(arr.at(-1)); // 'd'
console.log(arr.at(-2)); // 'c'

相比 arr[arr.length - 1]at(-1) 更直观。

3.2 includes():判断某个值在不在

const arr = [1, 2, 3, NaN];

console.log(arr.includes(2)); // true
console.log(arr.includes(9)); // false
console.log(arr.includes(NaN)); // true

如果你的目标只是“是否存在这个值”,优先考虑 includes()

3.3 indexOf() / lastIndexOf():找到值的位置

const arr = ['a', 'b', 'c', 'b'];

console.log(arr.indexOf('b')); // 1
console.log(arr.lastIndexOf('b')); // 3
console.log(arr.indexOf('x')); // -1

它适合找“某个确定值”的位置,但如果你要按条件查找,应该换 find 系列。

3.4 find() / findIndex():按条件找

const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
];

const user = users.find((item) => item.id === 2);
const index = users.findIndex((item) => item.id === 2);

console.log(user); // { id: 2, name: '李四' }
console.log(index); // 1

你可以这样记:

  • 找元素本身find()
  • 找元素下标findIndex()

3.5 findLast() / findLastIndex():从后往前找

const nums = [1, 4, 7, 8, 10];

console.log(nums.findLast((n) => n % 2 === 0)); // 10
console.log(nums.findLastIndex((n) => n % 2 === 0)); // 4

这两个方法在“找最后一个满足条件的数据”时比先 reverse() 再找更安全,因为它们不会改原数组

3.6 some() / every():做条件判断

const scores = [88, 92, 76, 95];

console.log(scores.some((n) => n < 80)); // true
console.log(scores.every((n) => n >= 60)); // true

你可以把它们理解成:

  • some()至少一个
  • every()全部都要

3.7 一张表快速区分

需求推荐方法
判断值是否存在includes()
找某个值第一次出现的位置indexOf()
找第一个满足条件的元素find()
找第一个满足条件元素的下标findIndex()
找最后一个满足条件的元素findLast()
是否至少一个满足条件some()
是否全部满足条件every()

4. 遍历、转换与聚合

4.1 forEach():只遍历,不产出新数组

const arr = [1, 2, 3];

arr.forEach((item, index) => {
console.log(index, item);
});

特点:

  • 适合“遍历执行副作用”,比如打印日志、改 DOM、发埋点
  • 没有返回值价值,不要指望它生成新数组

4.2 map():一一映射,得到等长新数组

const prices = [10, 20, 30];
const doubled = prices.map((n) => n * 2);

console.log(doubled); // [20, 40, 60]

适合场景:

  • 后端字段改名
  • 数据格式转换
  • 给列表增加展示字段
map() 最常见的坑

回调里忘了 return,结果会得到一个装满 undefined 的新数组。

4.3 filter():按条件筛选

const nums = [1, 2, 3, 4, 5, 6];
const evenNums = nums.filter((n) => n % 2 === 0);

console.log(evenNums); // [2, 4, 6]

它不会改原数组,只会把满足条件的项收集出来。

4.4 flat():把嵌套数组拍平

const arr = [1, [2, 3], [4, [5, 6]]];

console.log(arr.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]

默认只拍平一层。

4.5 flatMap():先映射,再拍平一层

const sentences = ['hello world', 'js array'];

const words = sentences.flatMap((item) => item.split(' '));
console.log(words); // ['hello', 'world', 'js', 'array']

如果你本来要写:

arr.map(...).flat();

那么可以考虑直接用 flatMap()

4.6 reduce():把多个值汇总成一个结果

reduce() 是数组方法里最强、也最容易让初学者绕晕的方法。它的核心只有一句话:

把数组中的每一项不断“累计”起来,最后得到一个结果。

const nums = [1, 2, 3, 4];

const total = nums.reduce((sum, n) => sum + n, 0);
console.log(total); // 10

参数含义:

  • sum:累计值
  • n:当前项
  • 0:初始值

常见用途:

  • 求和、求平均值
  • 统计词频
  • 按字段分组
  • 把数组转对象

统计词频示例:

const words = ['js', 'css', 'js', 'html', 'js'];

const countMap = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});

console.log(countMap);
// { js: 3, css: 1, html: 1 }

4.7 reduceRight():从右往左聚合

const arr = ['a', 'b', 'c'];

const result = arr.reduceRight((acc, cur) => acc + cur, '');
console.log(result); // 'cba'

它不常用,但在“从右往左组合”时有价值。


5. 增删改:什么时候该用哪一个?

5.1 push() / pop():操作数组尾部

const arr = [1, 2];

arr.push(3);
console.log(arr); // [1, 2, 3]

const last = arr.pop();
console.log(last); // 3
console.log(arr); // [1, 2]

这是最常见、也通常性能最友好的头尾操作之一。

5.2 shift() / unshift():操作数组头部

const arr = [2, 3];

arr.unshift(1);
console.log(arr); // [1, 2, 3]

const first = arr.shift();
console.log(first); // 1
console.log(arr); // [2, 3]

语义很直观,但大量头部插入 / 删除通常没有尾部操作高效。

5.3 splice():最灵活的“手术刀”

splice(start, deleteCount, ...items) 可以同时做三件事:

  • 删除
  • 插入
  • 替换
const arr = ['a', 'b', 'c', 'd'];

arr.splice(1, 2, 'x', 'y');
console.log(arr); // ['a', 'x', 'y', 'd']

这行代码的含义是:

  • 从下标 1 开始
  • 删除 2
  • 再插入 'x''y'

5.4 slice():截取一段,但不改原数组

const arr = ['a', 'b', 'c', 'd'];

const part = arr.slice(1, 3);
console.log(part); // ['b', 'c']
console.log(arr); // ['a', 'b', 'c', 'd']

你可以这样硬记:

  • splice 有字母 p,像“patch”,会动原数组
  • slice 像“切一片出来”,不动原数组

5.5 concat():合并数组

const arr1 = [1, 2];
const arr2 = [3, 4];

const merged = arr1.concat(arr2, [5, 6]);
console.log(merged); // [1, 2, 3, 4, 5, 6]

它不会修改原数组。

5.6 fill():批量填充值

const arr = new Array(5).fill(0);
console.log(arr); // [0, 0, 0, 0, 0]

arr.fill(9, 1, 4);
console.log(arr); // [0, 9, 9, 9, 0]

很适合:

  • 初始化固定长度数组
  • 批量占位
fill() 的引用类型坑

如果用对象去 fill,每一项会指向同一个对象引用。

const arr = new Array(3).fill({ done: false });
arr[0].done = true;

console.log(arr);
// [{ done: true }, { done: true }, { done: true }]

5.7 copyWithin():把数组内部一段复制到另一段

const arr = [1, 2, 3, 4, 5];

arr.copyWithin(0, 3);
console.log(arr); // [4, 5, 3, 4, 5]

这个方法不常作为业务主力,但你至少要知道它:

  • 会修改原数组
  • 是“数组内部复制”,不是扩容

6. 排序、反转与“不变写法”

6.1 reverse():原地反转

const arr = [1, 2, 3];

arr.reverse();
console.log(arr); // [3, 2, 1]

注意:它会改原数组。

6.2 sort():默认按字符串排序

这是数组题里的头号高频坑。

const nums = [1, 10, 2, 20];

nums.sort();
console.log(nums); // [1, 10, 2, 20]

为什么不是 [1, 2, 10, 20]

因为默认会先转成字符串,再按字典序比较。

数字排序要这样写:

const nums = [1, 10, 2, 20];

nums.sort((a, b) => a - b); // 升序
console.log(nums); // [1, 2, 10, 20]

nums.sort((a, b) => b - a); // 降序
console.log(nums); // [20, 10, 2, 1]
sort 的比较函数怎么记
  • a - b:升序
  • b - a:降序

6.3 toSorted() / toReversed() / toSpliced() / with():现代不变写法

这组方法很值得掌握,因为它们兼顾了“语义清晰”和“避免副作用”。

const arr = [3, 1, 2];

const sorted = arr.toSorted((a, b) => a - b);
const reversed = arr.toReversed();
const replaced = arr.with(1, 99);
const spliced = arr.toSpliced(1, 1, 88);

console.log(arr); // [3, 1, 2]
console.log(sorted); // [1, 2, 3]
console.log(reversed); // [2, 1, 3]
console.log(replaced); // [3, 99, 2]
console.log(spliced); // [3, 88, 2]

它们特别适合:

  • React / Vue 这类强调不可变数据的场景
  • 你不想在多人协作里引入“原地修改”的隐式副作用
兼容性提醒

这几个新方法属于较新的 ECMAScript 能力。旧运行环境里要提前确认兼容性,必要时配合构建工具或 Polyfill。


7. 转字符串与迭代器方法

7.1 join():把数组拼成字符串

const arr = ['2026', '03', '09'];

console.log(arr.join('-')); // '2026-03-09'
console.log(arr.join('')); // '20260309'

7.2 toString():默认用逗号连接

console.log([1, 2, 3].toString()); // '1,2,3'

日常开发里更常主动用 join(),因为分隔符可控。

7.3 keys() / values() / entries():拿迭代器

const arr = ['a', 'b', 'c'];

for (const index of arr.keys()) {
console.log(index);
}

for (const value of arr.values()) {
console.log(value);
}

for (const [index, value] of arr.entries()) {
console.log(index, value);
}

它们适合在 for...of 里配合使用。


8. 高频坑点:比“会写”更重要

8.1 sort() 默认不是数字排序

这是最容易考、最容易写错的一题:

[2, 11, 3].sort(); // [11, 2, 3]

8.2 slice()splice() 经常混

  • slice():不改原数组
  • splice():改原数组

8.3 forEach() 不能 break,也不适合直接配 await

arr.forEach(async (item) => {
await fetchItem(item);
});

这段代码常见问题是:你以为它会“一个接一个等”,但实际上 forEach 不会等待整体完成。

如果你需要串行异步,更常见的是:

for (const item of arr) {
await fetchItem(item);
}

8.4 reduce() 最好总是写初始值

const sum = [1, 2, 3].reduce((acc, cur) => acc + cur, 0);

这样更清晰,也能避免空数组时报错。

8.5 浅拷贝不是深拷贝

slice()concat()、展开运算符 ... 都只是浅拷贝

const arr = [{ count: 1 }];
const copied = arr.slice();

copied[0].count = 99;

console.log(arr[0].count); // 99

因为里面的对象引用还是同一个。

8.6 稀疏数组(空位)行为不完全一样

const arr = [1, , 3];

arr.map((x) => x * 2); // [2, empty, 6]
arr.includes(undefined); // true
arr.indexOf(undefined); // -1

这说明:

  • 有些方法会“跳过空位”
  • 有些方法会把空位当成 undefined 处理

日常开发里最稳妥的做法是:尽量不要主动制造稀疏数组


9. 实战里怎么选:一套够用判断法

如果你只想快速选方法,可以直接套下面这张表:

你现在的需求优先考虑
我只想知道某个值在不在includes()
我想找到符合条件的第一项find()
我想保留符合条件的所有项filter()
我想把每一项转成新结构map()
我想把数组合成一个值reduce()
我想复制一段数组slice()
我想在任意位置增删改splice()
我想排序但不污染原数组toSorted()
我想改某一项但不污染原数组with()
我想拿最后一个元素at(-1)

高频面试题(含参考答案)

Q1:map()forEach() 的区别是什么?

答: map() 会返回一个新数组,适合“数据转换”;forEach() 主要用于遍历执行副作用,不适合拿来产出新数组。

Q2:slice()splice() 的区别是什么?

答: slice() 用来截取数组片段,不修改原数组;splice() 用来在任意位置删除、插入、替换元素,会修改原数组。

Q3:为什么 [10, 2, 1].sort() 的结果不是数字升序?

答: 因为 sort() 默认按字符串字典序比较,不是按数字大小比较。数字排序要写比较函数,如 arr.sort((a, b) => a - b)

Q4:find()filter() 的区别是什么?

答: find() 返回第一个满足条件的元素,找到就停;filter() 会遍历完整个数组,把所有满足条件的元素组成一个新数组。

Q5:includes()indexOf() 都能判断存在,怎么选?

答: 如果你只关心“在不在”,优先 includes(),语义更直接,而且它能正确判断 NaN;如果你还需要位置,再考虑 indexOf()

Q6:reduce() 的本质是什么?

答: 本质是“累计器模式”。它会把数组每一项不断累积到一个结果里,这个结果可以是数字、对象、数组、Map,甚至任意复杂结构。

Q7:哪些数组方法会修改原数组?

答: 高频需要记住的有:pushpopshiftunshiftsplicesortreversefillcopyWithin

Q8:为什么在 React 里更推荐 toSorted()toSpliced()with() 这类写法?

答: 因为它们不会原地修改原数组,更符合不可变数据的思路,状态变化更清晰,也更不容易引入副作用。