操作数组的方法
a. 改变原数组 (Mutator Methods) - 要小心使用!
方法 | 描述 | 返回值 |
---|---|---|
push() | 在数组末尾添加一个或多个元素 | 新的 length |
pop() | 删除并返回数组的最后一个元素 | 被删除的元素 |
unshift() | 在数组开头添加一个或多个元素 | 新的 length |
shift() | 删除并返回数组的第一个元素 | 被删除的元素 |
splice() |
万能方法:删除、插入、替换元素 | 被删除元素组成的数组 |
sort() | 对数组进行原地排序 | 排序后的数组 |
reverse() | 原地反转数组元素的顺序 | 反转后的数组 |
fill() | 用一个固定值填充数组中从起始索引到终止索引内的全部元素 | 修改后的数组 |
copyWithin() | 浅复制数组的一部分到同一数组中的另一个位置 | 修改后的数组 |
重点讲解 splice 和 sort:
-
splice(start, deleteCount, ...items)
jsconst months = ['Jan', 'March', 'April', 'June']; // 插入元素 months.splice(1, 0, 'Feb'); // 在索引1处,删除0个元素,插入'Feb' // months -> ['Jan', 'Feb', 'March', 'April', 'June'] // 替换元素 months.splice(4, 1, 'May'); // 在索引4处,删除1个元素,插入'May' // months -> ['Jan', 'Feb', 'March', 'April', 'May'] // 删除元素 const removed = months.splice(2, 2); // 从索引2开始,删除2个元素 // months -> ['Jan', 'Feb', 'May'] // removed -> ['March', 'April']
-
sort(compareFunction)
-
默认行为 :将元素转换为字符串,然后按 UTF-16 码元值排序。这对于数字排序是灾难性的!
jsconst numbers = [10, 5, 40, 25]; numbers.sort(); // -> [10, 25, 40, 5] (因为 '10' < '25' < '40' < '5')
-
正确用法:必须提供一个比较函数。
js// 升序 numbers.sort((a, b) => a - b); // -> [5, 10, 25, 40] // 降序 numbers.sort((a, b) => b - a); // -> [40, 25, 10, 5]
. 不改变原数组,返回新数组 (Immutable Methods) - 推荐使用!
-
方法 | 描述 | 返回值 |
---|---|---|
slice() | 提取数组的一部分,返回一个新数组 | 包含提取元素的新数组 |
concat() | 合并两个或多个数组 | 一个新的合并后的数组 |
join() | 将数组所有元素连接成一个字符串 | 字符串 |
map() | 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果 | 一个新的、经过转换的数组 |
filter() | 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 | 一个新的、经过过滤的数组 |
reduce() | 对数组中的每个元素执行一个 "reducer" 函数,将其结果汇总为单个返回值 | 函数累计处理的结果 |
重点讲解 map, filter, reduce (函数式编程三巨头):
-
map(callback(element, index, array)) :转换
codeJavaScript
iniconst users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]; const names = users.map(user => user.name); // -> ['Alice', 'Bob'] const userDivs = users.map(user => `<div>${user.name}</div>`); // 在 React 中非常常用
-
filter(callback(element, index, array)) :筛选
codeJavaScript
iniconst numbers = [1, 2, 3, 4, 5, 6]; const evens = numbers.filter(num => num % 2 === 0); // -> [2, 4, 6]
-
reduce(callback(accumulator, currentValue, currentIndex, array), initialValue) :聚合/汇总
- accumulator:累加器,上一次回调的返回值。
- currentValue:当前处理的元素。
- initialValue:可选,累加器的初始值。强烈建议总是提供初始值!
codeJavaScript
ini// 1. 求和 const sum = [1, 2, 3, 4].reduce((acc, cur) => acc + cur, 0); // -> 10 // 2. 数组去重 const uniqueArr = [1, 2, 2, 3, 4, 3].reduce((acc, cur) => { if (!acc.includes(cur)) { acc.push(cur); } return acc; }, []); // -> [1, 2, 3, 4] // 3. 将对象数组按属性分组 const people = [ { name: 'Alice', age: 21 }, { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ]; const groupedByAge = people.reduce((acc, person) => { const age = person.age; if (!acc[age]) { acc[age] = []; } acc[age].push(person); return acc; }, {}); // -> { '20': [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ], '21': [ { name: 'Alice', age: 21 } ] }
c. 查找与判断方法 (通常不改变原数组)
方法 | 描述 | 返回值 |
---|---|---|
indexOf() | 返回指定元素在数组中第一次出现的索引,否则返回 -1 | 索引或 -1 |
lastIndexOf() | 返回指定元素在数组中最后一次出现的索引,否则返回 -1 | 索引或 -1 |
includes() | 判断一个数组是否包含一个指定的值 (ES6) | true 或 false |
find() | 返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined (ES6) | 元素值或 undefined |
findIndex() | 返回数组中满足提供的测试函数的第一个元素的索引,否则返回 -1 (ES6) | 索引或 -1 |
some() | 测试数组中是不是至少有1个元素通过了被提供的函数测试 | true 或 false |
every() | 测试一个数组内的所有元素是否都能通过某个指定函数的测试 | true 或 false |
includes vs indexOf:includes 更直观,且能正确处理 NaN,而 indexOf 不能。
codeJavaScript
scss
[1, 2, NaN].indexOf(NaN); // -> -1 (错误)
[1, 2, NaN].includes(NaN); // -> true (正确)
4. ES6+ 新特性
-
展开语法 (Spread Syntax ...) :非常强大,用于浅拷贝、合并数组、函数传参等。
codeJavaScript
iniconst arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; // 浅拷贝 const copiedArr = [...arr1]; // 合并数组 const mergedArr = [...arr1, ...arr2]; // -> [1, 2, 3, 4, 5, 6] // 字符串转数组 const chars = [...'hello']; // -> ['h', 'e', 'l', 'l', 'o']
-
解构赋值 (Destructuring Assignment) :快速从数组中提取值。
codeJavaScript
iniconst [first, second, , fourth] = [10, 20, 30, 40]; // first -> 10, second -> 20, fourth -> 40 // 配合剩余参数 const [head, ...tail] = [1, 2, 3, 4, 5]; // head -> 1, tail -> [2, 3, 4, 5]
5. 性能考量与最佳实践
-
Immutability 优先:在 React/Vue/Angular 等框架中,状态更新依赖于不可变性。优先使用 map, filter, reduce, slice, ...展开语法, 以及最新的 toSorted 等方法,而不是 push, splice, sort。这能避免很多难以追踪的 bug。
-
警惕 unshift 和 shift:这两个方法在数组头部操作,会导致所有后续元素的索引重新计算,时间复杂度是 O(n)。对于大数据量的数组,性能开销很大。而 push 和 pop 在尾部操作,是 O(1),非常快。如果需要频繁在头部操作,可以考虑使用双端队列(Deque)等更合适的数据结构。
-
选择合适的遍历方式:
- 需要性能极致且可以 break/continue:for 循环。
- 简洁且只需遍历,不关心返回值:forEach。
- 需要转换/筛选并返回新数组:map / filter。
- 需要遍历可迭代对象(不只是数组):for...of。
- 避免使用 for...in 遍历数组,它会遍历数组原型链上的属性,且顺序不保证。
-
Array.from vs ... :两者都可以将类数组对象转为数组。Array.from 更强大,因为它有第二个 map 功能的参数,可以在转换的同时处理元素,一步到位。
6. 数组的遍历方式总结
方式 | 优点 | 缺点 |
---|---|---|
for 循环 | 性能最高,可使用 break, continue | 写法相对繁琐 |
forEach | 语法简洁,语义清晰 | 无法中途跳出循环(break 无效) |
for...of | 语法简洁,可遍历所有可迭代对象,可使用 break, continue | 无法直接获取索引(需配合 entries()) |
map/filter | 函数式,链式调用,返回新数组,代码清晰 | 专用于转换/筛选,不应仅为遍历而用 |
for...in | 不要用于遍历数组 | 遍历的是键(key),会遍历原型链,顺序不定 |
while / do...while | 灵活,可控制循环条件 | 容易写出死循环 |
数组去重的写法
1、用set
js
let arr = [1,2,3,4,5,6,0,10,0]
console.log([...new Set(arr)]);
2、用reduce
js
let arr = [1,2,3,4,5,6,0,10,0]
let arr2 = arr.reduce((acc, cur) => {
if(!acc.includes(cur)){
acc.push(cur)
}
return acc
},[])
console.log(arr2);