数组方法
问题
JavaScript 数组有哪些常用方法?如何区分改变原数组和不改变原数组的方法?
答案
JavaScript 数组方法可分为改变原数组和不改变原数组两类。ES6+ 还引入了许多新的数组方法。
方法分类
改变原数组的方法
push / pop
const arr = [1, 2, 3];
// push: 末尾添加,返回新长度
const length = arr.push(4, 5);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(length); // 5
// pop: 末尾移除,返回移除的元素
const last = arr.pop();
console.log(arr); // [1, 2, 3, 4]
console.log(last); // 5
shift / unshift
const arr = [1, 2, 3];
// unshift: 开头添加,返回新长度
const length = arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]
console.log(length); // 4
// shift: 开头移除,返回移除的元素
const first = arr.shift();
console.log(arr); // [1, 2, 3]
console.log(first); // 0
splice
const arr = [1, 2, 3, 4, 5];
// splice(start, deleteCount, ...items)
// 删除
const deleted = arr.splice(1, 2);
console.log(arr); // [1, 4, 5]
console.log(deleted); // [2, 3]
// 插入
arr.splice(1, 0, 'a', 'b');
console.log(arr); // [1, 'a', 'b', 4, 5]
// 替换
arr.splice(1, 2, 'x');
console.log(arr); // [1, 'x', 4, 5]
// 负数索引
const arr2 = [1, 2, 3, 4, 5];
arr2.splice(-2, 1);
console.log(arr2); // [1, 2, 3, 5]
sort
const arr = [3, 1, 4, 1, 5, 9];
// 默认按字符串排序
arr.sort();
console.log(arr); // [1, 1, 3, 4, 5, 9]
// 数字排序需要比较函数
const nums = [10, 2, 30, 4];
nums.sort((a, b) => a - b); // 升序
console.log(nums); // [2, 4, 10, 30]
nums.sort((a, b) => b - a); // 降序
console.log(nums); // [30, 10, 4, 2]
// 对象数组排序
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 20 },
{ name: 'Charlie', age: 30 }
];
users.sort((a, b) => a.age - b.age);
// [{ name: 'Bob', age: 20 }, ...]
reverse
const arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]
fill
const arr = [1, 2, 3, 4, 5];
// fill(value, start?, end?)
arr.fill(0);
console.log(arr); // [0, 0, 0, 0, 0]
const arr2 = [1, 2, 3, 4, 5];
arr2.fill(0, 1, 3);
console.log(arr2); // [1, 0, 0, 4, 5]
// 创建数组
const zeros = new Array(5).fill(0);
console.log(zeros); // [0, 0, 0, 0, 0]
不改变原数组的方法
map
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] - 原数组不变
// 完整参数
const result = numbers.map((value, index, array) => {
return value * index;
});
console.log(result); // [0, 2, 6]
// 对象数组
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob']
filter
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6]
// 过滤对象数组
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 35, active: true }
];
const activeUsers = users.filter(user => user.active);
// [{ name: 'Alice'... }, { name: 'Charlie'... }]
// 去除假值
const arr = [0, 1, '', 'hello', null, undefined, false, true];
const truthy = arr.filter(Boolean);
console.log(truthy); // [1, 'hello', true]
reduce
const numbers = [1, 2, 3, 4, 5];
// reduce(callback, initialValue)
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15
// 不提供初始值,用第一个元素作为初始值
const product = numbers.reduce((acc, cur) => acc * cur);
console.log(product); // 120
// 实用场景
// 1. 数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {} as Record<number, typeof users[0]>);
// 2. 数组扁平化
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, cur) => acc.concat(cur), [] as number[]);
console.log(flat); // [1, 2, 3, 4, 5]
// 3. 统计出现次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {} as Record<string, number>);
// { apple: 3, banana: 2, orange: 1 }
// 4. 管道函数
const pipe = (...fns: ((x: number) => number)[]) =>
(x: number) => fns.reduce((v, fn) => fn(v), x);
const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;
const square = (x: number) => x * x;
const compute = pipe(addOne, double, square);
console.log(compute(2)); // ((2 + 1) * 2) ^ 2 = 36
find / findIndex
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
// find: 返回第一个匹配的元素
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Bob' }
// findIndex: 返回第一个匹配的索引
const index = users.findIndex(u => u.id === 2);
console.log(index); // 1
// 未找到
const notFound = users.find(u => u.id === 999);
console.log(notFound); // undefined
const notFoundIndex = users.findIndex(u => u.id === 999);
console.log(notFoundIndex); // -1
// ES2023: findLast / findLastIndex
const numbers = [1, 2, 3, 2, 1];
const lastTwo = numbers.findLast(n => n === 2);
console.log(lastTwo); // 2(索引3的那个)
const lastTwoIndex = numbers.findLastIndex(n => n === 2);
console.log(lastTwoIndex); // 3
every / some
const numbers = [2, 4, 6, 8];
// every: 所有元素都满足条件
const allEven = numbers.every(n => n % 2 === 0);
console.log(allEven); // true
// some: 至少一个元素满足条件
const hasLarge = numbers.some(n => n > 5);
console.log(hasLarge); // true
// 空数组
[].every(() => false); // true(空数组 every 返回 true)
[].some(() => true); // false(空数组 some 返回 false)
slice
const arr = [1, 2, 3, 4, 5];
// slice(start?, end?)
const sliced = arr.slice(1, 4);
console.log(sliced); // [2, 3, 4]
console.log(arr); // [1, 2, 3, 4, 5] - 原数组不变
// 负数索引
arr.slice(-2); // [4, 5]
arr.slice(1, -1); // [2, 3, 4]
// 复制数组
const copy = arr.slice();
concat
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combined = arr1.concat(arr2, arr3);
console.log(combined); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2] - 原数组不变
// 混合参数
const result = [1].concat(2, [3, 4], 5);
console.log(result); // [1, 2, 3, 4, 5]
flat / flatMap
// flat: 扁平化数组
const nested = [1, [2, [3, [4]]]];
nested.flat(); // [1, 2, [3, [4]]]
nested.flat(2); // [1, 2, 3, [4]]
nested.flat(Infinity); // [1, 2, 3, 4]
// flatMap: map + flat(1)
const sentences = ['Hello world', 'Good morning'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'world', 'Good', 'morning']
// 等价于
sentences.map(s => s.split(' ')).flat();
includes / indexOf
const arr = [1, 2, 3, NaN];
// includes: 返回布尔值
arr.includes(2); // true
arr.includes(NaN); // true(可以检测 NaN)
// indexOf: 返回索引
arr.indexOf(2); // 1
arr.indexOf(NaN); // -1(无法检测 NaN)
arr.indexOf(999); // -1
// 从指定位置开始
arr.includes(2, 2); // false
arr.indexOf(2, 2); // -1
ES2023 新增方法
const arr = [3, 1, 2];
// toSorted: 不改变原数组的 sort
const sorted = arr.toSorted((a, b) => a - b);
console.log(sorted); // [1, 2, 3]
console.log(arr); // [3, 1, 2]
// toReversed: 不改变原数组的 reverse
const reversed = arr.toReversed();
console.log(reversed); // [2, 1, 3]
console.log(arr); // [3, 1, 2]
// toSpliced: 不改变原数组的 splice
const spliced = arr.toSpliced(1, 1, 'x');
console.log(spliced); // [3, 'x', 2]
console.log(arr); // [3, 1, 2]
// with: 不改变原数组的索引赋值
const withNew = arr.with(1, 'x');
console.log(withNew); // [3, 'x', 2]
console.log(arr); // [3, 1, 2]
方法对比表
| 方法 | 改变原数组 | 返回值 | 用途 |
|---|---|---|---|
| push | ✅ | 新长度 | 末尾添加 |
| pop | ✅ | 移除元素 | 末尾移除 |
| shift | ✅ | 移除元素 | 开头移除 |
| unshift | ✅ | 新长度 | 开头添加 |
| splice | ✅ | 删除元素 | 增删改 |
| sort | ✅ | 排序后数组 | 排序 |
| reverse | ✅ | 反转后数组 | 反转 |
| fill | ✅ | 填充后数组 | 填充 |
| map | ❌ | 新数组 | 映射 |
| filter | ❌ | 新数组 | 过滤 |
| reduce | ❌ | 累积值 | 聚合 |
| find | ❌ | 元素/undefined | 查找元素 |
| findIndex | ❌ | 索引/-1 | 查找索引 |
| every | ❌ | 布尔值 | 全部满足 |
| some | ❌ | 布尔值 | 部分满足 |
| slice | ❌ | 新数组 | 截取 |
| concat | ❌ | 新数组 | 合并 |
| flat | ❌ | 新数组 | 扁平化 |
常见面试问题
Q1: map 和 forEach 的区别?
答案:
| 特性 | map | forEach |
|---|---|---|
| 返回值 | 新数组 | undefined |
| 链式调用 | ✅ | ❌ |
| 用途 | 转换数据 | 遍历执行 |
// map: 转换并返回新数组
const doubled = [1, 2, 3].map(n => n * 2);
// forEach: 仅遍历,无返回值
[1, 2, 3].forEach(n => console.log(n));
Q2: 如何用 reduce 实现 map?
答案:
function myMap<T, U>(arr: T[], fn: (item: T, index: number) => U): U[] {
return arr.reduce((acc, item, index) => {
acc.push(fn(item, index));
return acc;
}, [] as U[]);
}
myMap([1, 2, 3], n => n * 2); // [2, 4, 6]
Q3: 如何数组去重?
答案:
const arr = [1, 2, 2, 3, 3, 3];
// 方法 1: Set
const unique1 = [...new Set(arr)];
// 方法 2: filter
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);
// 方法 3: reduce
const unique3 = arr.reduce((acc, item) => {
if (!acc.includes(item)) acc.push(item);
return acc;
}, [] as number[]);
// 对象数组去重(按 id)
const users = [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
{ id: 1, name: 'c' }
];
const uniqueUsers = users.filter(
(user, index, self) => self.findIndex(u => u.id === user.id) === index
);
Q4: 数组扁平化的几种方法?
答案:
const nested = [1, [2, [3, [4]]]];
// 方法 1: flat
nested.flat(Infinity);
// 方法 2: reduce + 递归
function flatten(arr: any[]): any[] {
return arr.reduce((acc, item) => {
return acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
// 方法 3: toString(仅限数字)
nested.toString().split(',').map(Number);
// 方法 4: 栈
function flattenStack(arr: any[]): any[] {
const stack = [...arr];
const result: any[] = [];
while (stack.length) {
const item = stack.pop()!;
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
Q5: for...of 和 for...in 的区别?
答案:
| 特性 | for...of | for...in |
|---|---|---|
| 遍历内容 | 值 | 键(索引) |
| 适用对象 | 可迭代对象 | 对象属性 |
| 原型链 | 不遍历 | 会遍历 |
| 数组推荐 | ✅ | ❌ |
const arr = [1, 2, 3];
// for...of 遍历值
for (const value of arr) {
console.log(value); // 1, 2, 3
}
// for...in 遍历索引
for (const index in arr) {
console.log(index); // '0', '1', '2'
}