大家好,我是你们的老朋友FogLetter,今天我们来聊聊JavaScript中那些让人又爱又恨的数组方法。作为一名前端开发者,数组就像是我们的瑞士军刀,而数组方法就是刀上的各种工具,用对了事半功倍,用错了...那就准备加班吧!
数组:前端开发的"万金油"
在我们开始之前,先来看一个真实场景:假设我们需要管理一个用户列表,每个用 户有姓名、年龄和角色信息:
const people = [
{ name: '张三', age: 18, role: 'admin' },
{ name: '李四', age: 19, role: 'user' },
{ name: '王五', age: 20, role: 'user' }
];
现在我们要对这些用户进行各种操作:添加、删除、查找、过滤等等。这时候,数组方法就派上用场了!
第一类:会修改原数组的"危险分子"
这些方法就像是你家里的熊孩子,会用各种方式改变原始数组。使用时务必小心!
1. 栈和队列操作:push/pop/shift/unshift
这四个方法是数组的基本操作,它们模拟了栈(后进先出)和队列(先进先出)的行为。
// 栈操作:在数组末尾操作
const stack = [1, 2, 3];
stack.push(4); // [1, 2, 3, 4] - 添加元素到末尾
stack.pop(); // [1, 2, 3] - 移除最后一个元素
// 队列操作:在数组开头操作
const queue = [1, 2, 3];
queue.shift(); // [2, 3] - 移除第一个元素
queue.unshift(0); // [0, 2, 3] - 添加元素到开头
注意:shift和unshift的性能较差,因为它们需要移动数组中所有元素的索引。对于大型数组,请谨慎使用!
业务场景:实现一个简单的消息队列,新消息入队(push),处理消息时出队(shift)。
2. splice:数组的"瑞士军刀"
splice方法功能强大,可以实现删除、插入和替换操作:
const arr = [1, 2, 3, 4, 5];
// 删除:从索引2开始删除2个元素
const removed = arr.splice(2, 2);
console.log(removed); // [3, 4]
console.log(arr); // [1, 2, 5]
// 插入:从索引2开始插入元素
arr.splice(2, 0, 3, 4);
console.log(arr); // [1, 2, 3, 4, 5]
// 替换:从索引2开始替换2个元素
arr.splice(2, 2, 'a', 'b');
console.log(arr); // [1, 2, 'a', 'b', 5]
业务场景:在任务列表中,用户可以删除、添加或替换任务。
3. sort和reverse:排序和反转
let arr = [3, 1, 2];
// 默认排序(按字典序)
console.log(arr.sort()); // [1, 2, 3]
console.log(arr); // [1, 2, 3] - 原数组被修改!
// 数字排序
arr = [10, 1, 20, 3, 5];
console.log(arr.sort((a, b) => a - b)); // 升序: [1, 3, 5, 10, 20]
console.log(arr.sort((a, b) => b - a)); // 降序: [20, 10, 5, 3, 1]
// 反转数组
console.log(arr.reverse()); // [5, 3, 20, 10, 1] (假设arr当前是[1, 3, 5, 10, 20])
注意:sort方法默认按字符串Unicode码点排序,所以[10, 1, 20, 3, 5].sort()会得到[1, 10, 20, 3, 5],这通常不是我们想要的数字排序结果。
业务场景:商品列表按价格、销量或评分排序。
第二类:不会修改原数组的"安全卫士"
这些方法是函数式编程的拥护者,它们不会改变原数组,而是返回新数组或新值。推荐多多使用!
1. forEach和map:遍历和转换
const numbers = [1, 2, 3];
// forEach: 遍历但无返回值
numbers.forEach((num, index) => {
console.log(`索引${index}的值是${num}`);
});
// map: 遍历并返回新数组
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] - 原数组不变!
业务场景:map常用于将数据转换为JSX元素(React)或转换数据格式。
2. 查找元素:从indexOf到findLast
JavaScript提供了多种查找数组元素的方法:
const fruits = ['apple', 'banana', 'orange', 'banana'];
// ES5方法
console.log(fruits.indexOf('banana')); // 1
console.log(fruits.lastIndexOf('banana')); // 3
// ES6方法
console.log(fruits.includes('orange')); // true
const people = [
{ name: '张三', age: 18 },
{ name: '李四', age: 19 },
{ name: '王五', age: 20 }
];
// find和findIndex: 查找对象元素
const adult = people.find(person => person.age >= 19);
console.log(adult); // { name: '李四', age: 19 }
const adultIndex = people.findIndex(person => person.age >= 19);
console.log(adultIndex); // 1
// ES2023新增: findLast和findLastIndex
// const lastAdult = people.findLast(person => person.age >= 19);
// console.log(lastAdult); // { name: '王五', age: 20 }
注意:ES2023新增的findLast和findLastIndex方法在某些环境中可能还不支持,使用时请检查兼容性。
业务场景:在用户列表中查找特定用户或检查某个用户是否存在。
3. filter、every和some:过滤和判断
const numbers = [1, 2, 3, 4, 5];
// filter: 过滤满足条件的元素
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// every: 所有元素都满足条件
const allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true
// some: 至少一个元素满足条件
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true
// 业务场景示例
const allAdults = people.every(person => person.age >= 18);
const hasAdmin = people.some(person => person.role === 'admin');
业务场景:权限检查(所有用户都是成人吗?)、特征检测(有管理员用户吗?)等。
4. concat和slice:拼接和裁剪
const arr1 = [1, 2];
const arr2 = [3, 4];
// concat: 拼接数组
const combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4]
// slice: 裁剪数组 (start, end)
const arr = [1, 2, 3, 4, 5];
const part = arr.slice(1, 4);
console.log(part); // [2, 3, 4]
console.log(arr); // [1, 2, 3, 4, 5] - 原数组不变!
// 使用slice实现不修改原数组的"删除"操作
const removed = arr.slice(0, 2).concat(arr.slice(4));
console.log(removed); // [1, 2, 5]
console.log(arr); // [1, 2, 3, 4, 5] - 原数组保持不变
业务场景:分页功能(使用slice获取当前页的数据)、合并多个数据源。
5. flat和flatMap:扁平化数组
// 扁平化嵌套数组
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat()); // [1, 2, [3, [4]]]
console.log(nested.flat(2)); // [1, 2, 3, [4]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4]
// flatMap: 先map再flat(1)
const sentences = ["Hello world", "Good morning"];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words); // ["Hello", "world", "Good", "morning"]
业务场景:处理API返回的嵌套数据、展开标签列表等。
6. join和toString:数组转字符串
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.join()); // "apple,banana,orange"
console.log(fruits.join(' - ')); // "apple - banana - orange"
console.log(fruits.toString()); // "apple,banana,orange"
业务场景:生成CSV数据、显示标签列表等。
7. reduce和reduceRight:强大的归约操作
reduce是数组方法中最强大的之一,它可以实现几乎所有其他数组操作的功能:
const numbers = [1, 2, 3, 4, 5];
// 求和
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 15
// 找出最大值
const max = numbers.reduce((max, num) => num > max ? num : max, numbers[0]);
console.log(max); // 5
// 数组转对象
const people = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const peopleById = people.reduce((obj, person) => {
obj[person.id] = person;
return obj;
}, {});
console.log(peopleById);
// {
// 1: { id: 1, name: 'Alice' },
// 2: { id: 2, name: 'Bob' },
// 3: { id: 3, name: 'Charlie' }
// }
reduceRight与reduce类似,但是从右向左遍历数组。
业务场景:数据聚合、转换数据结构、实现复杂的计算。
第三类:静态方法和其它实用方法
1. Array.isArray(): 检查是否为数组
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray('hello')); // false
console.log(Array.isArray({})); // false
为什么不用instanceof Array? 在跨iframe的情况下,instanceof可能会失败,而Array.isArray()总是可靠的。
2. Array.from(): 从类数组对象创建数组
// 从字符串创建数组
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']
// 从Set创建数组
const set = new Set([1, 2, 3]);
console.log(Array.from(set)); // [1, 2, 3]
// 从类数组对象(如arguments)创建数组
function example() {
return Array.from(arguments);
}
console.log(example(1, 2, 3)); // [1, 2, 3]
// 使用map函数
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]
3. Array.of(): 创建包含任意类型元素的数组
// 与Array构造函数的区别
console.log(Array(3)); // [empty × 3] - 创建长度为3的空数组
console.log(Array(1, 2, 3)); // [1, 2, 3]
console.log(Array.of(3)); // [3] - 创建包含单个元素3的数组
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
第四类:ES6+新增的迭代器方法
ES6引入了迭代器协议,数组也提供了相关方法:
const fruits = ['apple', 'banana', 'orange'];
// keys(): 获取索引的迭代器
for (const index of fruits.keys()) {
console.log(index); // 0, 1, 2
}
// values(): 获取值的迭代器
for (const value of fruits.values()) {
console.log(value); // 'apple', 'banana', 'orange'
}
// entries(): 获取键值对的迭代器
for (const [index, value] of fruits.entries()) {
console.log(index, value); // 0 'apple', 1 'banana', 2 'orange'
}
性能考虑和最佳实践
-
选择正确的方法:根据需求选择最合适的方法。例如,如果只需要检查数组中是否存在某个元素,使用
includes比findIndex更合适。 -
链式调用:由于许多数组方法返回新数组,我们可以链式调用它们:
const result = numbers .filter(num => num % 2 === 0) .map(num => num * 2) .reduce((sum, num) => sum + num, 0); -
避免过度的链式调用:虽然链式调用很优雅,但每个方法都会创建一个新数组,对于大型数组可能会影响性能。
-
考虑使用for循环:对于性能敏感的场景,传统的for循环可能比数组方法更快。
总结
JavaScript数组方法为我们提供了强大而灵活的工具集,可以分为以下几类:
- 修改原数组的方法:push、pop、shift、unshift、splice、sort、reverse等
- 不修改原数组的方法:concat、slice、map、filter、reduce等
- 查找和判断方法:indexOf、includes、find、some、every等
- 转换方法:join、toString、Array.from等
- 迭代器方法:keys、values、entries等
理解这些方法的区别和适用场景,能够帮助我们编写出更简洁、更高效、更易维护的代码。记住,选择合适的方法往往比使用最酷的方法更重要!
希望这篇笔记能帮助你在JavaScript数组的世界中游刃有余。如果有任何问题或补充,欢迎在评论区留言讨论!
Happy Coding! 🚀