数组常用方法面试题全解析
一、核心要点速览
💡 核心考点
- 修改原数组: 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, 302. 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 = 现代化语法 ✨