跳到主要内容

数组方法

问题

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 的区别?

答案

特性mapforEach
返回值新数组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...offor...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'
}

相关链接