JavaScript 数组核心方法(非常全面)
一、核心概念
JavaScript 数组的内置方法是开发中最常用的工具,也是面试高频考点,核心:
- 功能:方法的核心作用
- 返回值:执行后返回什么(新数组 / 单个值 /undefined)
- 可变性:是否修改原数组(mutability)
- 使用场景:与其他方法的对比和选型
| 分类 | 包含方法 | 核心特征 |
|---|---|---|
| 迭代遍历方法 | forEach、map、filter、reduce、some、every | 遍历数组元素并执行逻辑 |
| 修改原数组(Mutator) | push、pop、shift、unshift、splice、sort、reverse | 直接改变原数组,有副作用 |
| 返回新数组(Non-mutating) | map、filter、concat、slice | 原数组不变,返回新数组 |
| 查找判断方法 | find、findIndex、includes、indexOf | 查找元素或判断存在性 |
二、为什么要深入理解?
- 代码简洁高效:用高阶方法替代冗余的 for 循环,提升代码可读性和维护性;
- 避免副作用:明确可变性,防止在 React/Vue/Redux 等状态管理中误改原数据;
- 面试核心考点:手动实现 map/reduce、区分 slice/splice、判断方法可变性是高频面试题;
- 性能优化:不同方法的执行效率不同(如 reduce 比嵌套循环更高效)。
三、核心方法详解(附多场景示例)
1. 迭代遍历方法
(1) map () - 数组转换(返回新数组)工作中最常使用
- 核心:遍历数组,对每个元素执行回调,返回新数组(长度与原数组一致)
- 可变性:不修改原数组
- 返回值:新数组
- 适用场景:数据格式转换、批量处理元素
基础示例:
const numbers = [1, 4, 9]; const doubles = numbers.map(num => num * 2); console.log(doubles); // [2, 8, 18] console.log(numbers); // [1, 4, 9](原数组不变)实战示例:对象数组属性提取
const users = [ { id: 1, name: '张三', age: 20 }, { id: 2, name: '李四', age: 25 }, { id: 3, name: '王五', age: 30 } ]; // 提取所有用户名 const userNames = users.map(user => user.name); console.log(userNames); // ["张三", "李四", "王五"] // 批量修改属性(返回新对象) const adultUsers = users.map(user => ({ ...user, isAdult: user.age >= 18 // 新增属性 })); console.log(adultUsers[0]); // { id: 1, name: '张三', age: 20, isAdult: true }(2) filter () - 数组过滤(返回新数组)
- 核心:遍历数组,筛选出符合条件的元素,返回新数组
- 可变性:不修改原数组
- 返回值:新数组(长度≤原数组)
- 适用场景:数据筛选、条件过滤
基础示例:
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction']; const longWords = words.filter(word => word.length > 6); console.log(longWords); // ["exuberant", "destruction"] [].filter(Boolean) //可以过滤掉null 实战示例:多条件过滤
const products = [ { name: '手机', price: 2999, category: '数码', stock: 50 }, { name: '耳机', price: 199, category: '数码', stock: 0 }, { name: 'T恤', price: 99, category: '服饰', stock: 100 }, { name: '键盘', price: 499, category: '数码', stock: 30 } ]; // 筛选:数码类、价格<500、有库存的商品 const validProducts = products.filter(p => p.category === '数码' && p.price < 500 && p.stock > 0 ); console.log(validProducts); // [{ name: '耳机', ... }, { name: '键盘', ... }](3) reduce () - 数组归并(返回单个值)
- 核心:遍历数组,将元素 “累积” 为单个值(数字、对象、数组等)
- API:
arr.reduce((acc, cur, idx, arr) => {}, initialValue)acc:累加器(上一次回调的返回值 / 初始值)cur:当前元素initialValue:可选,累加器初始值(推荐必传)
- 可变性:不修改原数组
- 适用场景:求和 / 求积、数组转对象、扁平数组、分组统计
基础示例:求和
const array = [1, 2, 3, 4]; const sum = array.reduce((acc, cur) => acc + cur, 0); console.log(sum); // 10实战示例 1:数组转对象(按 ID 映射)
const users = [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ]; // 转换为 { 1: { id:1, name:'张三' }, 2: { ... } } const userMap = users.reduce((acc, cur) => { acc[cur.id] = cur; return acc; }, {}); console.log(userMap[2]); // { id: 2, name: '李四' }实战示例 2:扁平嵌套数组
const nestedArr = [1, [2, [3, 4], 5], 6]; const flatArr = nestedArr.reduce((acc, cur) => { return acc.concat(Array.isArray(cur) ? cur.reduce((a, c) => a.concat(c), []) : cur); }, []); console.log(flatArr); // [1, 2, 3, 4, 5, 6]实战示例 3:分组统计
const scores = [ { name: '张三', subject: '数学', score: 90 }, { name: '张三', subject: '语文', score: 85 }, { name: '李四', subject: '数学', score: 88 }, { name: '李四', subject: '语文', score: 92 } ]; // 按姓名分组,统计总分 const scoreSum = scores.reduce((acc, cur) => { if (!acc[cur.name]) { acc[cur.name] = 0; } acc[cur.name] += cur.score; return acc; }, {}); console.log(scoreSum); // { 张三: 175, 李四: 180 }(4) forEach () - 遍历执行(无返回值)
- 核心:遍历数组,对每个元素执行回调(仅执行操作,无返回值)
- 可变性:本身不修改原数组,但回调中可手动修改
- 返回值:undefined
- 适用场景:执行副作用操作(如打印、调用 API、修改 DOM)
示例:
const fruits = ['苹果', '香蕉', '橙子']; // 打印每个元素 fruits.forEach((fruit, index) => { console.log(`第${index+1}个水果:${fruit}`); }); // 第1个水果:苹果 | 第2个水果:香蕉 | 第3个水果:橙子 // 注意:forEach 无法中断遍历(break 无效),需用 some/every 替代(5) some ()/every () - 条件判断
- some:只要有一个元素满足条件,返回 true(短路遍历)
- every:所有元素满足条件,返回 true(短路遍历)
- 返回值:布尔值
- 适用场景:判断数组是否符合某个条件
示例:
const numbers = [10, 20, 30, 35]; // some:是否有元素>30 const hasBigNum = numbers.some(num => num > 30); console.log(hasBigNum); // true // every:是否所有元素>15 const allBigNum = numbers.every(num => num > 15); console.log(allBigNum); // false(10不满足) // 实战:判断是否有已过期的订单 const orders = [ { id: 1, expired: false }, { id: 2, expired: true }, { id: 3, expired: false } ]; const hasExpired = orders.some(order => order.expired); console.log(hasExpired); // true2. 修改原数组的方法(Mutator)
(1) splice () - 增删改数组(核心)
- 核心:删除 / 插入 / 替换数组元素(原地修改)
- API:
arr.splice(start, deleteCount, item1, item2...)start:起始索引(负数表示从末尾开始)deleteCount:删除的元素数量(0 则不删除)item...:要插入的元素
- 返回值:被删除的元素组成的数组
- 可变性:修改原数组
示例 1:插入元素
const months = ['Jan', 'March', 'April', 'June']; // 索引1的位置,删除0个,插入'Feb' const removed = months.splice(1, 0, 'Feb'); console.log(months); // ["Jan", "Feb", "March", "April", "June"] console.log(removed); // [](无删除)示例 2:删除元素
const arr = [1, 2, 3, 4, 5]; // 索引2的位置,删除2个元素 const deleted = arr.splice(2, 2); console.log(arr); // [1, 2, 5] console.log(deleted); // [3, 4]示例 3:替换元素
const arr = ['a', 'b', 'c', 'd']; // 索引1的位置,删除1个,插入'x'和'y' arr.splice(1, 1, 'x', 'y'); console.log(arr); // ["a", "x", "y", "c", "d"](2) push/pop/shift/unshift - 数组首尾操作
| 方法 | 功能 | 返回值 | 示例 |
|---|---|---|---|
| push | 尾部添加元素 | 新数组长度 | const arr = [1,2]; arr.push(3); // arr=[1,2,3],返回3 |
| pop | 尾部删除元素 | 被删除的元素 | const arr = [1,2,3]; arr.pop(); // arr=[1,2],返回3 |
| unshift | 头部添加元素 | 新数组长度 | const arr = [2,3]; arr.unshift(1); // arr=[1,2,3],返回3 |
| shift | 头部删除元素 | 被删除的元素 | const arr = [1,2,3]; arr.shift(); // arr=[2,3],返回1 |
(3) sort/reverse - 排序 / 反转
- sort:原地排序,默认按字符串 Unicode 码排序(需传回调自定义规则)
- reverse:原地反转数组顺序
示例:
// sort 自定义排序(数字升序) const numbers = [10, 2, 25, 5]; numbers.sort((a, b) => a - b); console.log(numbers); // [2, 5, 10, 25] // reverse 反转 const arr = [1, 2, 3]; arr.reverse(); console.log(arr); // [3, 2, 1]3. 返回新数组的方法(Non-mutating)
(1) slice () - 截取数组片段
- 核心:截取数组的指定范围,返回新数组(浅拷贝)
- API:
arr.slice(start, end)(start 包含,end 不包含,负数表示末尾) - 可变性:不修改原数组
- 返回值:新数组
示例:
const arr = [1, 2, 3, 4, 5]; // 截取索引1到3(不包含3) const slice1 = arr.slice(1, 3); console.log(slice1); // [2, 3] // 截取最后2个元素 const slice2 = arr.slice(-2); console.log(slice2); // [4, 5] // 浅拷贝整个数组 const copyArr = arr.slice(); copyArr[0] = 10; console.log(arr); // [1, 2, 3, 4, 5](原数组不变)(2) concat () - 数组合并
- 核心:合并多个数组 / 值,返回新数组
- 可变性:不修改原数组
- 返回值:新数组
示例:
const arr1 = [1, 2]; const arr2 = [3, 4]; const arr3 = arr1.concat(arr2, 5); console.log(arr3); // [1, 2, 3, 4, 5] console.log(arr1); // [1, 2](原数组不变)4. 易混淆方法对比
(1) map vs forEach
| 维度 | map | forEach |
|---|---|---|
| 返回值 | 新数组 | undefined |
| 用途 | 数据转换,需返回新数组 | 执行操作(打印、调 API),无需返回值 |
| 链式调用 | 支持(map ().filter ()) | 不支持(返回 undefined) |
(2) slice vs splice
| 维度 | slice | splice |
|---|---|---|
| 可变性 | 不修改原数组 | 修改原数组 |
| 功能 | 截取数组片段 | 增删改数组元素 |
| 参数 | slice(start, end) | splice(start, deleteCount, ...items) |
| 返回值 | 截取的新数组 | 被删除的元素数组 |
(3) reduce 有无 initialValue 的区别
| 场景 | 有 initialValue | 无 initialValue |
|---|---|---|
| 空数组 | 返回 initialValue | 抛出 TypeError |
| 非空数组 | acc 初始值 = initialValue,从第一个元素开始遍历 | acc 初始值 = 第一个元素,从第二个元素开始遍历 |
示例:
// 有 initialValue(推荐) [1,2,3].reduce((acc, cur) => acc + cur, 0); // 6(acc初始=0,遍历1→2→3) // 无 initialValue [1,2,3].reduce((acc, cur) => acc + cur); // 6(acc初始=1,遍历2→3) // 空数组 + 无 initialValue → 报错 // [].reduce((acc, cur) => acc + cur); // Uncaught TypeError四、关键注意事项
- 可变性避坑:
- React/Vue 中,禁止直接修改 state 数组(如
state.arr.push(1)),需用非变异方法([...state.arr, 1]); - 如需修改原数组,先浅拷贝(
const copy = [...arr])再操作。
- React/Vue 中,禁止直接修改 state 数组(如
- 稀疏数组处理:
- map/forEach/filter 会跳过稀疏数组的空位(
[1,,3].map(x => x*2)→[2,,6]); - reduce 也会跳过空位(除非传 initialValue)。
- map/forEach/filter 会跳过稀疏数组的空位(
- 性能优化:
- 避免嵌套迭代(如 map 里套 filter),优先用 reduce 一次遍历;
- 大数据量遍历,for 循环比 forEach/map 更快(高阶方法有函数调用开销)。
手动实现核心方法(面试必考)
// 手动实现 map Array.prototype.myMap = function(callback) { const newArr = []; for (let i = 0; i < this.length; i++) { if (i in this) { // 处理稀疏数组 newArr[i] = callback(this[i], i, this); } } return newArr; }; // 手动实现 reduce Array.prototype.myReduce = function(callback, initialValue) { let acc = initialValue; let startIndex = 0; // 无 initialValue 时,acc 初始化为第一个元素 if (initialValue === undefined) { if (this.length === 0) throw new TypeError('空数组无初始值'); acc = this[0]; startIndex = 1; } for (let i = startIndex; i < this.length; i++) { if (i in this) { acc = callback(acc, this[i], i, this); } } return acc; };