Skip to content

数组常用方法面试题全解析

一、核心要点速览

💡 核心考点

  • 修改原数组: push/pop, shift/unshift, splice, sort, reverse
  • 不修改原数组: map/filter/reduce, slice, concat, forEach
  • 查找方法: find/findIndex, includes, indexOf
  • 转换方法: join, toString, toLocaleString
  • 高阶函数: map, filter, reduce, some, every

二、修改原数组的方法

1. 添加/删除元素

javascript
// ========== push() - 末尾添加 ==========
const arr = [1, 2, 3]
const newLength = arr.push(4, 5)
console.log(arr)        // [1, 2, 3, 4, 5]
console.log(newLength)  // 5(返回新长度)


// ========== pop() - 末尾删除 ==========
const arr = [1, 2, 3]
const removed = arr.pop()
console.log(arr)       // [1, 2]
console.log(removed)   // 3(返回删除的元素)


// ========== unshift() - 开头添加 ==========
const arr = [2, 3]
const newLength = arr.unshift(0, 1)
console.log(arr)        // [0, 1, 2, 3]
console.log(newLength)  // 4


// ========== shift() - 开头删除 ==========
const arr = [1, 2, 3]
const removed = arr.shift()
console.log(arr)       // [2, 3]
console.log(removed)   // 1


// ========== splice() - 任意位置增删 ==========
// 语法:splice(start, deleteCount, item1, item2, ...)

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

// 删除
const removed = arr.splice(2, 2)
console.log(arr)      // [1, 2, 5]
console.log(removed)  // [3, 4]

// 插入
arr.splice(2, 0, 'a', 'b')
console.log(arr)  // [1, 2, 'a', 'b', 5]

// 替换
arr.splice(1, 2, 'x')
console.log(arr)  // [1, 'x', 'b', 5]

2. 排序与反转

javascript
// ========== sort() - 排序 ==========
const arr = [3, 1, 4, 1, 5, 9, 2, 6]

// 默认按字符串排序(可能不符合预期)
arr.sort()
console.log(arr)  // [1, 1, 2, 3, 4, 5, 6, 9]

// 数字排序(需要比较函数)
arr.sort((a, b) => a - b)  // 升序
console.log(arr)  // [1, 1, 2, 3, 4, 5, 6, 9]

arr.sort((a, b) => b - a)  // 降序
console.log(arr)  // [9, 6, 5, 4, 3, 2, 1, 1]

// 对象数组排序
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 20 },
  { name: 'Charlie', age: 30 }
]

users.sort((a, b) => a.age - b.age)
console.log(users)  // 按年龄升序


// ========== reverse() - 反转 ==========
const arr = [1, 2, 3, 4, 5]
arr.reverse()
console.log(arr)  // [5, 4, 3, 2, 1]

// 注意:会修改原数组
// 如果想保持原数组,先复制
const original = [1, 2, 3]
const reversed = [...original].reverse()

3. 性能对比

┌──────────────────────────────────────────────────────────┐
│              添加/删除方法性能对比                        │
└──────────────────────────────────────────────────────────┘

时间复杂度:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
push: O(1)     ✓ 最快
pop: O(1)      ✓ 最快

unshift: O(n)  ⚠️ 需要移动所有元素
shift: O(n)    ⚠️ 需要移动所有元素

splice: O(n)   ⚠️ 需要移动元素

性能对比 (10000 个元素):
  push:  ████████ 0.1ms
  pop:   ████████ 0.1ms
  unshift: ████████████████████ 2ms
  shift: ████████████████████ 2ms
  splice: ████████████████████ 2ms

最佳实践:
✓ 优先使用 push/pop(栈操作)
⚠️ 避免频繁使用 unshift/shift
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

三、不修改原数组的方法

1. 切片与连接

javascript
// ========== slice() - 截取数组 ==========
const arr = [1, 2, 3, 4, 5]

// 基本用法
const sliced = arr.slice(1, 3)
console.log(sliced)  // [2, 3]
console.log(arr)     // [1, 2, 3, 4, 5](原数组不变)

// 省略参数
arr.slice(2)     // [3, 4, 5](从索引 2 到末尾)
arr.slice(-2)    // [4, 5](负数表示倒数)
arr.slice(1, -1) // [2, 3, 4]

// 复制整个数组
const copy = arr.slice()
const copy2 = [...arr]
const copy3 = Array.from(arr)


// ========== concat() - 合并数组 ==========
const arr1 = [1, 2]
const arr2 = [3, 4]
const arr3 = [5, 6]

const merged = arr1.concat(arr2, arr3)
console.log(merged)  // [1, 2, 3, 4, 5, 6]
console.log(arr1)    // [1, 2](原数组不变)

// 可以混合值
const result = arr1.concat(3, [4, 5], 6)
console.log(result)  // [1, 2, 3, 4, 5, 6]

2. 字符串转换

javascript
const arr = ['Vue', 'React', 'Angular']

// ========== join() - 连接为字符串 ==========
const str1 = arr.join()
console.log(str1)  // "Vue,React,Angular"(默认逗号)

const str2 = arr.join(' | ')
console.log(str2)  // "Vue | React | Angular"

const str3 = arr.join('')
console.log(str3)  // "VueReactAngular"


// ========== toString() ==========
const str = arr.toString()
console.log(str)  // "Vue,React,Angular"


// ========== toLocaleString() - 本地化 ==========
const prices = [100, 200, 300]
const formatted = prices.toLocaleString('zh-CN', {
  style: 'currency',
  currency: 'CNY'
})
console.log(formatted)  // "¥100.00,¥200.00,¥300.00"

四、遍历方法

1. forEach

javascript
// ========== forEach() - 遍历 ==========
const arr = [1, 2, 3, 4, 5]

arr.forEach((item, index, array) => {
  console.log(`索引${index}: ${item}`)
})

// 输出:
// 索引 0: 1
// 索引 1: 2
// 索引 2: 3
// 索引 3: 4
// 索引 4: 5

// 注意:forEach 无法用 break 中断
// 需要使用 for 循环或 some/every

// thisArg 参数
const obj = { multiplier: 10 }

[1, 2, 3].forEach(function(item) {
  console.log(item * this.multiplier)
}, obj)
// 输出:10, 20, 30

2. map - 映射

javascript
// ========== map() - 一对一映射 ==========
const arr = [1, 2, 3, 4, 5]

const doubled = arr.map(x => x * 2)
console.log(doubled)  // [2, 4, 6, 8, 10]
console.log(arr)      // [1, 2, 3, 4, 5](原数组不变)

// 实际应用:提取属性
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
]

const names = users.map(user => user.name)
console.log(names)  // ['Alice', 'Bob', 'Charlie']

// 链式调用
const result = [1, 2, 3, 4, 5]
  .map(x => x * 2)
  .filter(x => x > 5)
  .map(x => `数值:${x}`)

console.log(result)  // ['数值:6', '数值:8', '数值:10']

3. filter - 过滤

javascript
// ========== filter() - 过滤 ==========
const arr = [1, 2, 3, 4, 5, 6]

const evens = arr.filter(x => x % 2 === 0)
console.log(evens)  // [2, 4, 6]

// 实际应用:筛选数据
const users = [
  { name: 'Alice', age: 25, active: true },
  { name: 'Bob', age: 17, active: false },
  { name: 'Charlie', age: 30, active: true }
]

const adults = users.filter(user => user.age >= 18)
const activeUsers = users.filter(user => user.active)

// 组合使用
const result = users
  .filter(user => user.age >= 18)
  .filter(user => user.active)
  .map(user => user.name)

console.log(result)  // ['Alice', 'Charlie']

4. reduce - 归并

javascript
// ========== reduce() - 归并 ==========
// 语法:reduce(callback(accumulator, currentValue), initialValue)

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

// 求和
const sum = arr.reduce((acc, curr) => acc + curr, 0)
console.log(sum)  // 15

// 执行过程:
// acc=0, curr=1 → return 1
// acc=1, curr=2 → return 3
// acc=3, curr=3 → return 6
// acc=6, curr=4 → return 10
// acc=10, curr=5 → return 15


// 求最大值
const max = arr.reduce((acc, curr) => 
  acc > curr ? acc : curr, -Infinity
)
console.log(max)  // 5


// 数组扁平化
const nested = [1, [2, 3], [4, [5, 6]]]
const flat = nested.reduce((acc, curr) => 
  acc.concat(Array.isArray(curr) ? curr.flat() : curr)
, [])
console.log(flat)  // [1, 2, 3, 4, 5, 6]


// 统计词频
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const frequency = words.reduce((acc, word) => {
  acc[word] = (acc[word] || 0) + 1
  return acc
}, {})
console.log(frequency)  // { apple: 3, banana: 2, orange: 1 }


// 数组转对象
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]

const userMap = users.reduce((acc, user) => {
  acc[user.id] = user
  return acc
}, {})

console.log(userMap)
// {
//   1: { id: 1, name: 'Alice' },
//   2: { id: 2, name: 'Bob' }
// }

5. reduceRight

javascript
// ========== reduceRight() - 从右向左归并 ==========
const arr = [1, 2, 3, 4, 5]

const result = arr.reduceRight((acc, curr) => acc - curr, 0)
console.log(result)  // ((((0 - 5) - 4) - 3) - 2) - 1 = -15

// 与 reduce 对比
const leftToRight = arr.reduce((acc, curr) => acc - curr, 0)
console.log(leftToRight)  // (((((0 - 1) - 2) - 3) - 4) - 5) = -15

五、查找方法

1. 基础查找

javascript
const arr = [10, 20, 30, 40, 50]

// ========== indexOf() ==========
console.log(arr.indexOf(30))   // 2
console.log(arr.indexOf(25))   // -1(未找到)
console.log(arr.indexOf(30, 3)) // -1(从索引 3 开始找)

// ========== lastIndexOf() ==========
const nums = [1, 2, 3, 2, 1]
console.log(nums.lastIndexOf(2))  // 3
console.log(nums.lastIndexOf(2, 2)) // 1(从索引 2 向前找)

// ========== includes() ==========
console.log(arr.includes(30))     // true
console.log(arr.includes(25))     // false
console.log(arr.includes(30, 3))  // false

// 与 indexOf 对比
if (arr.indexOf(30) !== -1) { /* 找到了 */ }
if (arr.includes(30)) { /* 更简洁! */ }

2. find 系列

javascript
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 17 },
  { id: 3, name: 'Charlie', age: 30 }
]

// ========== find() - 查找第一个匹配项 ==========
const adult = users.find(user => user.age >= 18)
console.log(adult)  // { id: 1, name: 'Alice', age: 25 }

const notFound = users.find(user => user.age > 100)
console.log(notFound)  // undefined


// ========== findIndex() - 查找索引 ==========
const index = users.findIndex(user => user.age >= 18)
console.log(index)  // 0

const notFoundIndex = users.findIndex(user => user.name === 'David')
console.log(notFoundIndex)  // -1


// ========== findLast() / findLastIndex() (ES2023) ==========
const lastAdult = users.findLast(user => user.age >= 18)
console.log(lastAdult)  // { id: 3, name: 'Charlie', age: 30 }

3. 条件判断

javascript
const numbers = [1, 2, 3, 4, 5]

// ========== some() - 至少一个满足 ==========
const hasEven = numbers.some(n => n % 2 === 0)
console.log(hasEven)  // true

// 类似于:numbers.filter(n => n % 2 === 0).length > 0
// 但 some 更高效(找到就停止)

// ========== every() - 全部满足 ==========
const allPositive = numbers.every(n => n > 0)
console.log(allPositive)  // true

const allEven = numbers.every(n => n % 2 === 0)
console.log(allEven)  // false


// 实际应用场景
const formFields = [
  { name: 'username', valid: true },
  { name: 'email', valid: true },
  { name: 'password', valid: false }
]

// 表单是否全部验证通过
const isValid = formFields.every(field => field.valid)

// 是否有字段验证失败
const hasError = formFields.some(field => !field.valid)

六、ES6+ 新增方法

1. Array.from

javascript
// ========== Array.from() - 类数组转数组 ==========

// 字符串转数组
const str = 'Hello'
const arr = Array.from(str)
console.log(arr)  // ['H', 'e', 'l', 'l', 'o']

// NodeList 转数组
const divs = document.querySelectorAll('div')
const divArray = Array.from(divs)

// 带映射函数
const numbers = Array.from([1, 2, 3], x => x * 2)
console.log(numbers)  // [2, 4, 6]

// 创建指定长度的数组
const range = Array.from({ length: 5 }, (_, i) => i)
console.log(range)  // [0, 1, 2, 3, 4]

2. Array.of

javascript
// ========== Array.of() - 创建数组 ==========

// 解决 Array() 的歧义
const arr1 = Array(3)       // [empty × 3]
const arr2 = Array.of(3)    // [3]
const arr3 = Array.of(1, 2, 3)  // [1, 2, 3]

3. flat / flatMap

javascript
// ========== flat() - 扁平化 ==========
const nested = [1, [2, 3], [4, [5, 6]]]

const flat1 = nested.flat()
console.log(flat1)  // [1, 2, 3, 4, [5, 6]](只展平一层)

const flat2 = nested.flat(2)
console.log(flat2)  // [1, 2, 3, 4, 5, 6](展平两层)

const infinite = nested.flat(Infinity)
console.log(infinite)  // [1, 2, 3, 4, 5, 6](完全展平)


// ========== flatMap() - 先 map 后 flat ==========
const sentences = ['Hello World', 'JavaScript is fun', 'Vue.js']

const words = sentences.flatMap(sentence => sentence.split(' '))
console.log(words)  
// ['Hello', 'World', 'JavaScript', 'is', 'fun', 'Vue.js']

// 等价于
const words2 = sentences
  .map(sentence => sentence.split(' '))
  .flat()

4. at

javascript
// ========== at() - 支持负索引 ==========
const arr = [10, 20, 30, 40, 50]

console.log(arr.at(-1))   // 50(最后一个)
console.log(arr.at(-2))   // 40
console.log(arr.at(0))    // 10

// 传统写法
console.log(arr[arr.length - 1])  // 50(更繁琐)

七、性能优化与最佳实践

1. 方法选择指南

┌──────────────────────────────────────────────────────────┐
│                  数组方法选择指南                         │
└──────────────────────────────────────────────────────────┘

需求场景 → 推荐方法:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
遍历数组(不返回值)→ forEach
  ✓ 简单遍历
  ✗ 无法 break

遍历数组(返回新数组)→ map
  ✓ 一对一映射
  ✓ 链式调用

筛选元素 → filter
  ✓ 返回符合条件的元素
  ✓ 可链式调用

查找单个元素 → find / findIndex
  ✓ 找到第一个就停止
  ✓ 返回元素或索引

检查是否存在 → includes / some
  ✓ includes: 检查值
  ✓ some: 检查条件

全部满足条件 → every
  ✓ 短路优化

累加/归并 → reduce
  ✓ 功能最强大
  ⚠️ 语法稍复杂

添加/删除元素 → push/pop/shift/unshift/splice
  ✓ 会修改原数组
  ⚠️ 注意性能

截取/合并 → slice/concat
  ✓ 不修改原数组
  ✓ 安全
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2. 性能对比

javascript
// ========== 遍历性能对比 ==========
const arr = Array.from({ length: 10000 }, (_, i) => i)

// for 循环(最快)
console.time('for')
for (let i = 0; i < arr.length; i++) {
  arr[i] * 2
}
console.timeEnd('for')  // ~0.1ms

// forEach
console.time('forEach')
arr.forEach(x => x * 2)
console.timeEnd('forEach')  // ~0.3ms

// map
console.time('map')
arr.map(x => x * 2)
console.timeEnd('map')  // ~0.5ms

// for...of
console.time('forOf')
for (const x of arr) {
  x * 2
}
console.timeEnd('forOf')  // ~0.2ms

// 性能排名:
// for:      ████████     最快
// for...of: ██████████   
// forEach:  ████████████
// map:      ██████████████  (因为要创建新数组)


// ========== 查找性能对比 ==========
const largeArr = Array.from({ length: 100000 }, (_, i) => i)

// indexOf
console.time('indexOf')
largeArr.indexOf(99999)
console.timeEnd('indexOf')  // ~0.5ms

// find
console.time('find')
largeArr.find(x => x === 99999)
console.timeEnd('find')  // ~0.5ms

// includes
console.time('includes')
largeArr.includes(99999)
console.timeEnd('includes')  // ~0.3ms(最快)

// 结论:includes 最简单,性能最优

3. 内存效率

javascript
// ========== 避免不必要的中间数组 ==========

// ✗ 不好的做法(创建多个中间数组)
const result = arr
  .map(x => x * 2)      // 创建新数组 1
  .filter(x => x > 10)  // 创建新数组 2
  .map(x => x + 1)      // 创建新数组 3

// ✓ 更好的做法(使用 reduce)
const result = arr.reduce((acc, x) => {
  const val = x * 2
  if (val > 10) {
    acc.push(val + 1)
  }
  return acc
}, [])

// 或者保持可读性(现代引擎已优化)
const result = arr
  .map(x => x * 2)
  .filter(x => x > 10)
  .map(x => x + 1)


// ========== 大数组处理 ==========

// 使用生成器(节省内存)
function* generateNumbers() {
  for (let i = 0; i < 1000000; i++) {
    yield i
  }
}

const gen = generateNumbers()
console.log(gen.next().value)  // 0
console.log(gen.next().value)  // 1

// 使用迭代器
const iterator = arr[Symbol.iterator]()
console.log(iterator.next().value)

八、面试标准回答

数组方法是 JavaScript 开发中最常用的 API,我将其分为几大类:

修改原数组的方法包括:

  • push/pop:在末尾添加/删除元素,O(1) 时间复杂度
  • unshift/shift:在开头添加/删除元素,O(n) 时间复杂度
  • splice:在任意位置增删改,功能最强但也最慢
  • sort/reverse:排序和反转

不修改原数组的方法包括:

  • slice/concat:截取和合并数组
  • join/toString:转换为字符串

遍历方法是重点:

  • forEach:简单遍历,无返回值
  • map:一对一映射,返回新数组
  • filter:过滤,返回符合条件的元素
  • reduce:归并,功能最强大,可以实现各种聚合操作

查找方法

  • indexOf/lastIndexOf:基础查找
  • includes:检查是否存在(最简洁)
  • find/findIndex:按条件查找
  • some/every:条件判断

ES6+ 新增方法

  • Array.from:类数组转数组
  • flat/flatMap:数组扁平化
  • at:支持负索引

实际应用中,我经常使用 map/filter/reduce 进行数据处理,用 find 查找元素,用 includes 做存在性检查。对于性能敏感的场景,我会选择传统的 for 循环。

reduce 是最强大的方法,可以实现 map、filter 等功能,但代码可读性会下降。我的原则是:优先使用语义清晰的方法,只有在确实需要归并时才用 reduce。


九、记忆口诀

数组方法歌诀:

增删改查要分清,
push pop 是栈操作。
unshift shift 头操作,
splice 最强随意搞!

遍历三剑客:
map 映射一对一,
filter 过滤留需要的,
reduce 归并功能强!

查找方法多:
indexOf 找位置,
includes 判存在,
find 条件来筛选,
some every 做判断!

ES6 新特性:
from 转数组,
flat 来扁平,
at 支持负索引,
现代开发必备技!

十、推荐资源


十一、总结一句话

  • 修改原数组: push/pop + splice/sort = 直接操作数据 ⚠️
  • 不修改数组: map/filter/reduce + slice/concat = 函数式编程
  • 查找遍历: find/includes + forEach/every = 日常开发必备 🎯
  • ES6+: from/flat/at = 现代化语法
最近更新