JavaScript数组方法手册:从基础操作到ES2025新特性
什么是数组方法?
数组方法是JavaScript内置的一系列函数,它们让我们能够高效地操作数组中的数据。无论是添加、删除、查找、排序还是遍历元素,数组方法都能帮我们用简洁的代码完成复杂的数据处理任务。
// 一个简单的数组
let students = ['张三', '李四', '王五', '赵六'];
// 使用数组方法快速操作
console.log(students.join(' → ')); // 张三 → 李四 → 王五 → 赵六
students.push('钱七'); // 添加元素
console.log(students); // ['张三', '李四', '王五', '赵六', '钱七']
基础操作方法
1. toString() - 数组转字符串
将数组转换为逗号分隔的字符串,不会修改原数组。
let colors = ['红色', '蓝色', '绿色', '黄颜色'];
console.log(colors.toString()); // '红色,蓝色,绿色,黄颜色'
// 实际应用:快速打印数组内容
let scores = [85, 92, 78, 90];
console.log('成绩:' + scores.toString()); // 成绩:85,92,78,90
2. at() - 通过索引访问元素(ES2022)
使用正数或负数索引访问数组元素,负数从末尾开始计数。
let fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];
// 正数索引(从0开始)
console.log(fruits.at(0)); // 苹果
console.log(fruits.at(2)); // 橙子
// 负数索引(从-1开始)
console.log(fruits.at(-1)); // 西瓜(之后一个)
console.log(fruits.at(-2)); // 葡萄(倒数第二个)
// 传统写法对比
console.log(fruits[fruits.length - 1]); // 西瓜(传统方式)
个人经验:at()方法让访问数组末尾元素变得非常优雅,特别是在不知道数组长度的情况下。如果你需要兼容旧环境,可以用方括号语法作为替代。
// 兼容性写法
function safeAt(array, index) {
if (index >= 0) return array[index];
return array[array.length + index];
}
3. forEach() - 遍历数组
对数组的每个元素执行一次提供的函数。
let users = [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 },
{ name: '王五', age: 28 }
];
// 基本遍历
users.forEach(function(user) {
console.log(user.name + '今年' + user.age + '岁');
});
// 使用箭头函数,并访问索引
users.forEach((user, index) => {
console.log(`第${index + 1}位:${user.name}`);
});
// 计算年龄总和
let totalAge = 0;
users.forEach(user => {
totalAge += user.age;
});
console.log('平均年龄:', totalAge / users.length);
4. join() - 用指定分隔符合并元素
将数组所有元素连接成一个字符串,可以指定分隔符。
let words = ['JavaScript', '数组', '方法', '教程'];
// 默认逗号分隔
console.log(words.join()); // 'JavaScript,数组,方法,教程'
// 自定义分隔符
console.log(words.join(' ')); // 'JavaScript 数组 方法 教程'
console.log(words.join('-')); // 'JavaScript-数组-方法-教程'
console.log(words.join('')); // 'JavaScript数组方法教程'
// 实际应用:构建URL参数
let params = ['page=1', 'limit=10', 'keyword=JavaScript'];
let queryString = params.join('&');
console.log(queryString); // 'page=1&limit=10&keyword=JavaScript'
5. pop() - 删除之后一个元素
删除数组的之后一个元素,并返回该元素。
let tasks = ['任务1', '任务2', '任务3', '任务4'];
console.log('当前任务:', tasks);
// 完成之后一个任务
let completedTask = tasks.pop();
console.log('已完成:', completedTask); // 任务4
console.log('剩余任务:', tasks); // ['任务1', '任务2', '任务3']
// pop() 空数组返回 undefined
let empty = [];
console.log(empty.pop()); // undefined
6. push() - 在末尾添加元素
向数组末尾添加一个或多个元素,返回新数组的长度。
let cart = ['苹果', '香蕉'];
console.log('购物车:', cart);
// 添加单个商品
let newLength = cart.push('橙子');
console.log('新长度:', newLength); // 3
console.log('购物车:', cart); // ['苹果', '香蕉', '橙子']
// 添加多个商品
cart.push('葡萄', '西瓜');
console.log('购物车:', cart); // ['苹果', '香蕉', '橙子', '葡萄', '西瓜']
// 实际应用:合并数组
let newItems = ['芒果', '草莓'];
cart.push(...newItems); // 使用展开运算符
console.log('购物车:', cart); // 添加了芒果和草莓
7. shift() - 删除第一个元素
删除数组的第一个元素,并返回该元素,所有剩余元素前移。
let queue = ['顾客1', '顾客2', '顾客3', '顾客4'];
console.log('排队队列:', queue);
// 服务第一位顾客
let served = queue.shift();
console.log('正在服务:', served); // 顾客1
console.log('剩余队列:', queue); // ['顾客2', '顾客3', '顾客4']
// shift() 空数组返回 undefined
let empty = [];
console.log(empty.shift()); // undefined
8. unshift() - 在开头添加元素
向数组开头添加一个或多个元素,返回新数组的长度。
let priorityTasks = ['任务3', '任务4'];
console.log('初始任务:', priorityTasks);
// 添加高优先级任务
let newLength = priorityTasks.unshift('任务1', '任务2');
console.log('新长度:', newLength); // 4
console.log('更新后:', priorityTasks); // ['任务1', '任务2', '任务3', '任务4']
// 实际应用:消息队列优先级
let messages = ['普通消息1', '普通消息2'];
messages.unshift('紧急消息');
console.log(messages); // ['紧急消息', '普通消息1', '普通消息2']
9. concat() - 合并数组
合并两个或多个数组,返回新数组,不修改原数组。
let group1 = ['张三', '李四'];
let group2 = ['王五', '赵六'];
let group3 = ['钱七', '孙八'];
// 合并两个数组
let allStudents = group1.concat(group2);
console.log(allStudents); // ['张三', '李四', '王五', '赵六']
// 合并多个数组
let everyone = group1.concat(group2, group3);
console.log(everyone); // ['张三', '李四', '王五', '赵六', '钱七', '孙八']
// 合并数组和单个值
let combined = group1.concat('新同学', group2);
console.log(combined); // ['张三', '李四', '新同学', '王五', '赵六']
// 现在写法(展开运算符)
let modernCombine = [...group1, ...group2];
console.log(modernCombine); // ['张三', '李四', '王五', '赵六']
10. copyWithin() - 数组内复制
在数组内部将指定位置的元素复制到他位置,返回修改后的原数组。
let numbers = [1, 2, 3, 4, 5, 6];
// 将索引2开始的元素复制到索引0
numbers.copyWithin(0, 2);
console.log(numbers); // [3, 4, 5, 4, 5, 6]
let letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
// 将索引3到5的元素复制到索引1
letters.copyWithin(1, 3, 5);
console.log(letters); // ['A', 'D', 'E', 'D', 'E', 'F', 'G']
// 使用负数索引
let animals = ['cat', 'dog', 'fish', 'bird', 'rabbit'];
animals.copyWithin(-3, -2);
console.log(animals); // ['cat', 'dog', 'bird', 'rabbit', 'rabbit']
11. flat() - 扁平化数组
将嵌套数组"拉平"成为一维数组,可以指定扁平化深度。
let nestedArray = [1, [2, 3], [4, [5, 6]]];
// 默认扁平化一层
let flatOnce = nestedArray.flat();
console.log(flatOnce); // [1, 2, 3, 4, [5, 6]]
// 指定扁平化深度
let flatTwice = nestedArray.flat(2);
console.log(flatTwice); // [1, 2, 3, 4, 5, 6]
// 实际应用:处理多层嵌套数据
let data = [
{ category: '电子产品', items: ['手机', '电脑', '平板'] },
{ category: '服装', items: ['T恤', '裤子', '外套'] },
{ category: '食品', items: ['水果', '零食', '饮料'] }
];
let allItems = data.map(item => item.items).flat();
console.log(allItems); // ['手机', '电脑', '平板', 'T恤', '裤子', '外套', '水果', '零食', '饮料']
// 更简洁的写法:flatMap()
let allItems2 = data.flatMap(item => item.items);
console.log(allItems2); // 同上
12. splice() - 多功能操作
可以删除、插入、替换数组元素,直接修改原数组。
let months = ['一月', '二月', '三月', '四月', '五月', '六月'];
console.log('原始月份:', months);
// 1. 删除元素
let removed = months.splice(2, 2); // 从索引2删除2个元素
console.log('删除的元素:', removed); // ['三月', '四月']
console.log('删除后:', months); // ['一月', '二月', '五月', '六月']
// 2. 插入元素
months.splice(2, 0, '三月', '四月'); // 在索引2插入,不删除
console.log('插入后:', months); // ['一月', '二月', '三月', '四月', '五月', '六月']
// 3. 替换元素
months.splice(3, 1, '四月(修订)');
console.log('替换后:', months); // ['一月', '二月', '三月', '四月(修订)', '五月', '六月']
// 4. 综合操作:删除2个,插入3个
let colors = ['红', '绿', '蓝', '黄', '紫'];
colors.splice(1, 2, '橙', '青', '粉');
console.log(colors); // ['红', '橙', '青', '粉', '黄', '紫']
13. toSpliced() - 安全版splice(ES2025)
与splice功能相同,但返回新数组,不修改原数组。
let months = ['一月', '二月', '三月', '四月', '五月', '六月'];
// 创建修改后的新数组,原数组不变
let newMonths = months.toSpliced(2, 2, '三月(新)', '四月(新)');
console.log('原数组:', months); // ['一月', '二月', '三月', '四月', '五月', '六月']
console.log('新数组:', newMonths); // ['一月', '二月', '三月(新)', '四月(新)', '五月', '六月']
// 链式调用
let result = months
.toSpliced(1, 1, '二月(新)')
.toSpliced(3, 1, '五月(新)');
console.log(result);
个人见解:toSpliced()这类不修改原数组的方法在函数式编程中非常有用,特别是当我们需要保持数据不可变时。在React/Vue这类框架中,这种模式能避免意外的副作用。
14. slice() - 提取子数组
返回数组的一段切片,不修改原数组。
let fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜', '芒果'];
// 提取从索引1到索引4(不包括4)
let slice1 = fruits.slice(1, 4);
console.log(slice1); // ['香蕉', '橙子', '葡萄']
// 只指定起始位置
let slice2 = fruits.slice(2);
console.log(slice2); // ['橙子', '葡萄', '西瓜', '芒果']
// 使用负数索引
let slice3 = fruits.slice(-3);
console.log(slice3); // ['葡萄', '西瓜', '芒果']
let slice4 = fruits.slice(1, -2);
console.log(slice4); // ['香蕉', '橙子', '葡萄']
// 复制整个数组
let copy = fruits.slice();
console.log(copy); // 数组副本
查找和检测方法
1. indexOf() - 查找元素索引
返回指定元素在数组中首次出现的索引,找不到返回-1。
let colors = ['红色', '蓝色', '绿色', '蓝色', '黄颜色'];
console.log(colors.indexOf('蓝色')); // 1(第一次出现)
console.log(colors.indexOf('紫色')); // -1(不存在)
// 指定开始搜索的位置
console.log(colors.indexOf('蓝色', 2)); // 3(从索引2开始搜索)
// 实际应用:检查元素是否存在
function contains(array, element) {
return array.indexOf(element) !== -1;
}
console.log(contains(colors, '绿色')); // true
console.log(contains(colors, '黑色')); // false
2. lastIndexOf() - 从后查找索引
返回指定元素在数组中之后一次出现的索引。
let colors = ['红色', '蓝色', '绿色', '蓝色', '黄颜色'];
console.log(colors.lastIndexOf('蓝色')); // 3(之后一次出现)
console.log(colors.lastIndexOf('紫色')); // -1
// 指定结束搜索的位置
console.log(colors.lastIndexOf('蓝色', 2)); // 1(从后往前搜索到索引2)
3. includes() - 是否包含元素
判断数组是否包含指定元素,返回布尔值。
let products = ['手机', '电脑', '平板', '耳机'];
console.log(products.includes('电脑')); // true
console.log(products.includes('相机')); // false
// 指定起始搜索位置
console.log(products.includes('耳机', 2)); // true(从索引2开始)
console.log(products.includes('手机', 1)); // false(从索引1开始,找不到手机)
// includes 能正确识别 NaN
let numbers = [1, 2, NaN, 4];
console.log(numbers.includes(NaN)); // true
console.log(numbers.indexOf(NaN)); // -1(indexOf不能识别NaN)
4. find() - 查找符合条件的元素
返回第一个满足测试函数的元素值,找不到返回undefined。
let users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 },
{ id: 4, name: '赵六', age: 35 }
];
// 查找年龄大于30的第一个用户
let user = users.find(user => user.age > 30);
console.log(user); // { id: 4, name: '赵六', age: 35 }
// 查找名字包含'王'的用户
let user2 = users.find(user => user.name.includes('王'));
console.log(user2); // { id: 3, name: '王五', age: 28 }
// 查找不存在的条件
let user3 = users.find(user => user.age > 40);
console.log(user3); // undefined
5. findIndex() - 查找符合条件的索引
返回第一个满足测试函数的元素索引,找不到返回-1。
let users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 },
{ id: 4, name: '赵六', age: 35 }
];
// 查找年龄30岁的用户索引
let index = users.findIndex(user => user.age === 30);
console.log(index); // 1
// 查找名字为'王五'的用户
let index2 = users.findIndex(user => user.name === '王五');
console.log(index2); // 2
// 更新找到的用户
if (index2 !== -1) {
users[index2].age = 29;
}
console.log(users[2]); // { id: 3, name: '王五', age: 29 }
6. findLast() - 从后查找元素(ES2025)
从数组末尾开始查找,返回第一个满足条件的元素。
let users = [
{ id: 1, name: '张三', role: 'user' },
{ id: 2, name: '李四', role: 'admin' },
{ id: 3, name: '王五', role: 'user' },
{ id: 4, name: '赵六', role: 'admin' }
];
// 从后往前查找第一个管理员
let lastAdmin = users.findLast(user => user.role === 'admin');
console.log(lastAdmin); // { id: 4, name: '赵六', role: 'admin' }
// 对比find()是从前往后
let firstAdmin = users.find(user => user.role === 'admin');
console.log(firstAdmin); // { id: 2, name: '李四', role: 'admin' }
7. findLastIndex() - 从后查找索引(ES2025)
从数组末尾开始查找,返回第一个满足条件的元素索引。
let numbers = [5, 12, 8, 130, 44, 12, 9];
// 从后往前查找大于10的元素的索引
let lastIndex = numbers.findLastIndex(num => num > 10);
console.log(lastIndex); // 5(值为12的元素)
// 查找等于12的之后一个位置
let lastTwelve = numbers.findLastIndex(num => num === 12);
console.log(lastTwelve); // 5
// 不存在的条件
let notFound = numbers.findLastIndex(num => num > 200);
console.log(notFound); // -1
排序和顺序方法
1. sort() - 排序数组
对数组元素进行排序,默认按字符串Unicode码点排序,直接修改原数组。
// 字符串排序
let fruits = ['香蕉', '苹果', '橙子', '葡萄', '草莓'];
fruits.sort();
console.log(fruits); // ['苹果', '葡萄', '草莓', '香蕉', '橙子']
// 数字排序(需要提供比较函数)
let numbers = [45, 3, 27, 12, 8, 36, 19];
// 默认排序(转为字符串排序,结果不符合预期)
numbers.sort();
console.log('默认排序:', numbers); // [12, 19, 27, 3, 36, 45, 8]
// 升序排序
numbers.sort((a, b) => a - b);
console.log('升序:', numbers); // [3, 8, 12, 19, 27, 36, 45]
// 降序排序
numbers.sort((a, b) => b - a);
console.log('降序:', numbers); // [45, 36, 27, 19, 12, 8, 3]
// 随机排序
let items = [1, 2, 3, 4, 5];
items.sort(() => Math.random() - 0.5);
console.log('随机顺序:', items);
2. reverse() - 反转数组
反转数组中元素的顺序,直接修改原数组。
let letters = ['A', 'B', 'C', 'D', 'E'];
console.log('原始数组:', letters);
letters.reverse();
console.log('反转后:', letters); // ['E', 'D', 'C', 'B', 'A']
// 与sort结合实现降序排序
let scores = [85, 92, 78, 90, 88];
scores.sort((a, b) => a - b).reverse();
console.log('降序分数:', scores); // [92, 90, 88, 85, 78]
// 更高效的降序:直接使用比较函数
scores.sort((a, b) => b - a);
3. toSorted() - 安全排序(ES2025)
与sort功能相同,但返回新数组,不修改原数组。
let numbers = [45, 3, 27, 12, 8, 36, 19];
// 创建排序后的新数组
let sortedAsc = numbers.toSorted((a, b) => a - b);
let sortedDesc = numbers.toSorted((a, b) => b - a);
console.log('原数组:', numbers); // [45, 3, 27, 12, 8, 36, 19]
console.log('升序新数组:', sortedAsc); // [3, 8, 12, 19, 27, 36, 45]
console.log('降序新数组:', sortedDesc); // [45, 36, 27, 19, 12, 8, 3]
// 不提供比较函数时,默认按字符串排序
let fruits = ['香蕉', '苹果', '橙子'];
let sortedFruits = fruits.toSorted();
console.log(sortedFruits); // ['苹果', '香蕉', '橙子']
4. toReversed() - 安全反转(ES2025)
与reverse功能相同,但返回新数组,不修改原数组。
let letters = ['A', 'B', 'C', 'D', 'E'];
let reversed = letters.toReversed();
console.log('原数组:', letters); // ['A', 'B', 'C', 'D', 'E']
console.log('反转后:', reversed); // ['E', 'D', 'C', 'B', 'A']
// 链式调用
let result = [1, 2, 3, 4, 5]
.toReversed()
.map(x => x * 2);
console.log(result); // [10, 8, 6, 4, 2]
5. 对象数组排序
使用自定义比较函数对对象数组进行排序。
let students = [
{ name: '张三', age: 25, score: 85 },
{ name: '李四', age: 22, score: 92 },
{ name: '王五', age: 28, score: 78 },
{ name: '赵六', age: 23, score: 96 }
];
// 按年龄升序
let byAge = [...students].sort((a, b) => a.age - b.age);
console.log('按年龄排序:', byAge);
// 按分数降序
let byScore = [...students].sort((a, b) => b.score - a.score);
console.log('按分数排序:', byScore);
// 按姓名排序(中文需要localeCompare)
let byName = [...students].sort((a, b) => a.name.localeCompare(b.name));
console.log('按姓名排序:', byName);
// 多级排序:先按年龄,再按分数
let multiSort = [...students].sort((a, b) => {
if (a.age !== b.age) {
return a.age - b.age; // 先按年龄
}
return b.score - a.score; // 年龄相同按分数
});
console.log('多级排序:', multiSort);
课程知识要点
-
基础操作:push/pop(末尾)、unshift/shift(开头)、splice(任意位置)
-
转换方法:toString()、join()、concat()、flat()
-
提取方法:slice()、copyWithin()、splice()(可删可加)
-
查找方法:indexOf()、lastIndexOf()、includes()、find()、findIndex()
-
ES2022新特性:at()方法支持负数索引
-
ES2025新特性:toSorted()、toReversed()、toSpliced()、findLast()、findLastIndex()
-
排序方法:sort()、reverse(),以及它们的不可变版本
-
遍历方法:forEach()用于迭代
-
对象数组处理:自定义比较函数进行复杂排序
开发实践建议
基于多年JavaScript开发经验,我对数组方法的使用建议如下:
// 1. 链式调用的威力
let products = [
{ name: '手机', price: 3299, category: '电子产品' },
{ name: 'T恤', price: 99, category: '服装' },
{ name: '电脑', price: 5999, category: '电子产品' },
{ name: '裤子', price: 199, category: '服装' }
];
let result = products
.filter(p => p.category === '电子产品')
.sort((a, b) => a.price - b.price)
.map(p => p.name);
console.log(result); // ['手机', '电脑']
// 2. 优先使用不可变方法(ES2025+)
let original = [1, 2, 3, 4, 5];
let modified = original
.toReversed()
.toSpliced(2, 1, 10)
.toSorted((a, b) => a - b);
console.log('原数组不变:', original);
console.log('新数组:', modified);
// 3. 使用includes替代indexOf检查存在性
let items = ['apple', 'banana', 'orange'];
if (items.includes('banana')) {
console.log('找到了!'); // 更直观
}
// 4. 使用at()访问末尾元素
let arr = [10, 20, 30, 40, 50];
console.log(arr.at(-1)); // 50(更优雅)
console.log(arr[arr.length - 1]); // 50(传统方式)
// 5. 使用find/findIndex查找对象
let users = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }];
let user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: '李四' }
选择建议:
-
需要修改原数组:使用push/pop/shift/unshift/splice/sort/reverse
-
需要保持原数组不变:使用concat/slice/...展开运算符/toSorted/toReversed等
-
需要查找元素:使用includes(存在性)、find(元素)、findIndex(索引)
-
需要遍历:使用forEach(仅遍历)、map(转换)、filter(筛选)
-
需要排序:使用sort(修改原数组)或toSorted(返回新数组)
掌握这些数组方法,能让你的JavaScript代码更加简洁、高效、易读。数组是日常开发中最常用的数据结构之一,熟练运用这些方法,是每个JavaScript开发者必备的技能。