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查找元素或判断存在性

二、为什么要深入理解?

  1. 代码简洁高效:用高阶方法替代冗余的 for 循环,提升代码可读性和维护性;
  2. 避免副作用:明确可变性,防止在 React/Vue/Redux 等状态管理中误改原数据;
  3. 面试核心考点:手动实现 map/reduce、区分 slice/splice、判断方法可变性是高频面试题;
  4. 性能优化:不同方法的执行效率不同(如 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 () - 数组归并(返回单个值)

  • 核心:遍历数组,将元素 “累积” 为单个值(数字、对象、数组等)
  • APIarr.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); // true

2. 修改原数组的方法(Mutator)

(1) splice () - 增删改数组(核心)

  • 核心:删除 / 插入 / 替换数组元素(原地修改)
  • APIarr.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 () - 截取数组片段

  • 核心:截取数组的指定范围,返回新数组(浅拷贝)
  • APIarr.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

维度mapforEach
返回值新数组undefined
用途数据转换,需返回新数组执行操作(打印、调 API),无需返回值
链式调用支持(map ().filter ())不支持(返回 undefined)

(2) slice vs splice

维度slicesplice
可变性不修改原数组修改原数组
功能截取数组片段增删改数组元素
参数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

四、关键注意事项

  1. 可变性避坑
    • React/Vue 中,禁止直接修改 state 数组(如 state.arr.push(1)),需用非变异方法([...state.arr, 1]);
    • 如需修改原数组,先浅拷贝(const copy = [...arr])再操作。
  2. 稀疏数组处理
    • map/forEach/filter 会跳过稀疏数组的空位([1,,3].map(x => x*2)[2,,6]);
    • reduce 也会跳过空位(除非传 initialValue)。
  3. 性能优化
    • 避免嵌套迭代(如 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; };

 

Could not load content