JavaScript之数组方法详解
JavaScript之数组方法详解
- 一、数组的创建与基础特性
- 1.1 数组的创建方式
- 1.2 数组的核心特性
- 二、修改原数组的方法
- 2.1 添加/删除元素
- 2.1.1 `push()`:尾部添加元素
- 2.1.2 `pop()`:尾部删除元素
- 2.1.3 `unshift()`:头部添加元素
- 2.1.4 `shift()`:头部删除元素
- 2.1.5 `splice()`:指定位置添加/删除
- 2.2 排序与反转
- 2.2.1 `reverse()`:反转数组
- 2.2.2 `sort()`:排序数组
- 三、不修改原数组的方法
- 3.1 数组截取与合并
- 3.1.1 `slice()`:截取子数组
- 3.1.2 `concat()`:合并数组
- 3.2 元素查找
- 3.2.1 `indexOf()`与`lastIndexOf()`:查找索引
- 3.2.2 `includes()`:判断元素是否存在
- 3.2.3 `find()`与`findIndex()`:查找符合条件的元素
- 3.3 数组转换
- 3.3.1 `join()`:数组转字符串
- 3.3.2 `toString()`:数组转字符串(简化版)
- 四、遍历与迭代方法
- 4.1 基础遍历
- 4.1.1 `forEach()`:遍历元素
- 4.2 映射与过滤
- 4.2.1 `map()`:映射新数组
- 4.2.2 `filter()`:过滤元素
- 4.3 聚合与检测
- 4.3.1 `reduce()`:累加计算
- 4.3.2 `every()`与`some()`:条件检测
- 五、方法对比与最佳实践
- 5.1 方法选择指南
- 5.2 性能与注意事项
- 六、常见问题与避坑指南
- 6.1 `sort()`的默认排序陷阱
- 6.2 `indexOf()`无法识别`NaN`
- 6.3 `map()`返回新数组的长度不变
数组是JavaScript中最常用的数据结构之一,掌握数组方法是高效处理数据的基础,本文我将系统梳理JavaScript数组的常用方法,从基础的添加删除到复杂的遍历转换,并结合实例解析每种方法的用法、特性及适用场景,帮你彻底搞懂数组操作的核心技巧。
一、数组的创建与基础特性
在学习方法之前,先回顾数组的创建方式,这是后续操作的基础:
1.1 数组的创建方式
// 1. 字面量方式(最常用)
const arr1 = [1, 2, 3];// 2. 构造函数方式
const arr2 = new Array(3); // 创建长度为3的空数组([ , , ])
const arr3 = new Array(1, 2, 3); // 创建包含元素的数组([1,2,3])// 3. 静态方法创建(ES6+)
const arr4 = Array.of(1, 2, 3); // 类似字面量,避免new Array的歧义
const arr5 = Array.from([1, 2, 3], x => x * 2); // 从类数组/可迭代对象创建并映射([2,4,6])
1.2 数组的核心特性
- 动态长度:数组长度可自动扩展(如
arr.push(4)
会增加长度) - 元素类型不限:可包含数字、字符串、对象等任意类型
- 索引从0开始:通过
arr[index]
访问元素
二、修改原数组的方法
这类方法会直接改变原数组,操作时需注意对原数据的影响。
2.1 添加/删除元素
2.1.1 push()
:尾部添加元素
const fruits = ['apple', 'banana'];
const newLength = fruits.push('orange', 'grape'); // 添加多个元素
console.log(fruits); // ['apple', 'banana', 'orange', 'grape']
console.log(newLength); // 4(返回新长度)
特性:接受任意数量参数,添加到数组末尾,返回新长度。
2.1.2 pop()
:尾部删除元素
const lastFruit = fruits.pop(); // 删除最后一个元素
console.log(fruits); // ['apple', 'banana', 'orange']
console.log(lastFruit); // 'grape'(返回被删除的元素)
特性:无参数,删除最后一个元素,返回被删除元素。
2.1.3 unshift()
:头部添加元素
const newLength = fruits.unshift('mango'); // 头部添加
console.log(fruits); // ['mango', 'apple', 'banana', 'orange']
console.log(newLength); // 4(返回新长度)
特性:接受任意数量参数,添加到数组头部,返回新长度(性能比push()
差,因需移动所有元素)。
2.1.4 shift()
:头部删除元素
const firstFruit = fruits.shift(); // 删除第一个元素
console.log(fruits); // ['apple', 'banana', 'orange']
console.log(firstFruit); // 'mango'(返回被删除元素)
特性:无参数,删除第一个元素,返回被删除元素(性能较差,同unshift()
)。
2.1.5 splice()
:指定位置添加/删除
const numbers = [1, 2, 3, 4, 5];// 1. 删除:splice(起始索引, 删除数量)
const deleted = numbers.splice(2, 2); // 从索引2开始删除2个元素
console.log(numbers); // [1, 2, 5]
console.log(deleted); // [3, 4](返回被删除元素数组)// 2. 添加:splice(起始索引, 0, 添加元素1, ...)
numbers.splice(2, 0, 3, 4); // 从索引2开始,删除0个,添加3和4
console.log(numbers); // [1, 2, 3, 4, 5](恢复原数组)// 3. 替换:splice(起始索引, 删除数量, 替换元素)
numbers.splice(1, 2, 'a', 'b'); // 从索引1删除2个,添加'a','b'
console.log(numbers); // [1, 'a', 'b', 4, 5]
特性:功能强大,可同时完成添加和删除,返回被删除元素数组(若未删除则返回空数组)。
2.2 排序与反转
2.2.1 reverse()
:反转数组
const arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]
特性:直接反转原数组,返回反转后的数组(与原数组引用相同)。
2.2.2 sort()
:排序数组
// 1. 默认排序(按字符串Unicode码点,可能不符合预期)
const nums = [10, 2, 23];
nums.sort();
console.log(nums); // [10, 2, 23](错误排序,因'10'在'2'前)// 2. 数值升序排序(传入比较函数)
nums.sort((a, b) => a - b);
console.log(nums); // [2, 10, 23]// 3. 数值降序排序
nums.sort((a, b) => b - a);
console.log(nums); // [23, 10, 2]// 4. 对象数组排序(按age属性升序)
const users = [{ name: 'Bob', age: 25 },{ name: 'Alice', age: 20 }
];
users.sort((a, b) => a.age - b.age);
console.log(users); // [Alice(20), Bob(25)]
特性:默认按字符串排序,需传入比较函数(a,b) => a - b
(升序)或(a,b) => b - a
(降序)实现数值排序;直接修改原数组,返回排序后的数组。
三、不修改原数组的方法
这类方法会返回新数组或其他结果,原数组保持不变,适合函数式编程。
3.1 数组截取与合并
3.1.1 slice()
:截取子数组
const arr = [1, 2, 3, 4, 5];// 1. slice(起始索引, 结束索引):含头不含尾
const sub1 = arr.slice(1, 4); // 从索引1到3(不包含4)
console.log(sub1); // [2, 3, 4]// 2. 省略结束索引:截取到末尾
const sub2 = arr.slice(2); // 从索引2到末尾
console.log(sub2); // [3, 4, 5]// 3. 负数索引:从末尾开始计算
const sub3 = arr.slice(-3, -1); // 从倒数第3到倒数第2(索引2和3)
console.log(sub3); // [3, 4]
特性:返回新数组(原数组不变),参数支持负数(表示从末尾开始),slice(0)
可用于复制数组。
3.1.2 concat()
:合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5];// 合并多个数组
const merged = arr1.concat(arr2, arr3, 6); // 可添加非数组元素
console.log(merged); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2](原数组不变)
特性:合并多个数组或值,返回新数组(原数组不变),类似ES6的扩展运算符[...arr1, ...arr2]
。
3.2 元素查找
3.2.1 indexOf()
与lastIndexOf()
:查找索引
const fruits = ['apple', 'banana', 'apple', 'orange'];// indexOf(元素, 起始索引):从前往后找,返回首次出现的索引
console.log(fruits.indexOf('apple')); // 0
console.log(fruits.indexOf('apple', 1)); // 2(从索引1开始找)
console.log(fruits.indexOf('grape')); // -1(未找到)// lastIndexOf(元素):从后往前找,返回最后出现的索引
console.log(fruits.lastIndexOf('apple')); // 2
特性:indexOf
从头部开始,lastIndexOf
从尾部开始;返回元素索引,未找到返回-1
;使用严格相等(===
)比较,无法查找引用类型元素。
3.2.2 includes()
:判断元素是否存在
const nums = [1, 2, 3, NaN];// 基础用法
console.log(nums.includes(2)); // true
console.log(nums.includes(5)); // false// 特殊:能识别NaN(indexOf不能)
console.log(nums.includes(NaN)); // true
console.log(nums.indexOf(NaN)); // -1(indexOf的缺陷)
特性:返回布尔值(是否包含元素),支持NaN
判断(比indexOf
更友好),第二个参数可指定起始索引。
3.2.3 find()
与findIndex()
:查找符合条件的元素
const users = [{ id: 1, name: 'Alice' },{ id: 2, name: 'Bob' },{ id: 3, name: 'Alice' }
];// find(回调函数):返回第一个符合条件的元素
const alice = users.find(user => user.name === 'Alice');
console.log(alice); // { id: 1, name: 'Alice' }// findIndex(回调函数):返回第一个符合条件的元素索引
const aliceIndex = users.findIndex(user => user.name === 'Alice');
console.log(aliceIndex); // 0// 未找到时
console.log(users.find(user => user.id === 10)); // undefined
console.log(users.findIndex(user => user.id === 10)); // -1
特性:接受回调函数(item, index, arr) => 条件
,返回第一个符合条件的元素(find
)或索引(findIndex
);适合查找对象数组,支持复杂条件。
3.3 数组转换
3.3.1 join()
:数组转字符串
const arr = ['a', 'b', 'c'];// 1. 默认用逗号分隔
console.log(arr.join()); // "a,b,c"// 2. 指定分隔符
console.log(arr.join('-')); // "a-b-c"// 3. 空字符串分隔(拼接成连续字符串)
console.log(arr.join('')); // "abc"
特性:将数组元素拼接为字符串,返回字符串;与String.split()
互为逆操作。
3.3.2 toString()
:数组转字符串(简化版)
const arr = [1, 2, 3];
console.log(arr.toString()); // "1,2,3"(等价于join())
特性:默认用逗号分隔,功能简单,不如join()
灵活。
四、遍历与迭代方法
这类方法用于遍历数组并执行操作,是处理数组数据的核心工具。
4.1 基础遍历
4.1.1 forEach()
:遍历元素
const nums = [1, 2, 3];
let sum = 0;// forEach(回调函数):无返回值
nums.forEach((num, index, arr) => {sum += num;console.log(`索引${index}的值:${num}`);
});
console.log('总和:', sum); // 6
特性:无返回值(undefined
),无法通过break
中断遍历(需用try/catch
或return
跳过当前元素);适合简单的遍历操作。
4.2 映射与过滤
4.2.1 map()
:映射新数组
const numbers = [1, 2, 3, 4];// map(回调函数):返回新数组(每个元素为回调返回值)
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4](原数组不变)// 对象数组映射
const users = [{ name: 'A' }, { name: 'B' }];
const names = users.map(user => user.name);
console.log(names); // ['A', 'B']
特性:返回新数组(长度与原数组相同),原数组不变;适合数据转换(如从对象数组中提取特定属性)。
4.2.2 filter()
:过滤元素
const numbers = [1, 2, 3, 4, 5, 6];// filter(回调函数):返回符合条件的元素组成的新数组
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6]// 对象数组过滤
const users = [{ name: 'A', age: 17 },{ name: 'B', age: 20 },{ name: 'C', age: 25 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults); // [B(20), C(25)]
特性:返回新数组(包含所有符合条件的元素),原数组不变;适合数据筛选。
4.3 聚合与检测
4.3.1 reduce()
:累加计算
const numbers = [1, 2, 3, 4];// reduce(回调函数, 初始值):返回累加结果
const sum = numbers.reduce((acc, num) => {return acc + num; // acc为累加器,num为当前元素
}, 0); // 初始值为0
console.log('总和:', sum); // 10// 计算数组中每个元素出现的次数
const fruits = ['apple', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {acc[fruit] = (acc[fruit] || 0) + 1;return acc;
}, {}); // 初始值为对象
console.log(count); // { apple: 2, banana: 1 }
特性:从左到右遍历,通过累加器acc
聚合结果,功能强大(可实现sum
、max
、groupBy
等);reduceRight()
从右到左遍历,用法类似。
4.3.2 every()
与some()
:条件检测
const scores = [80, 90, 75, 60];// every():所有元素都符合条件才返回true
const allPass = scores.every(score => score >= 60);
console.log(allPass); // true(所有分数≥60)// some():至少一个元素符合条件就返回true
const hasExcellent = scores.some(score => score >= 90);
console.log(hasExcellent); // true(有一个分数≥90)
特性:every
类似“逻辑与”(全满足),some
类似“逻辑或”(有一个满足);返回布尔值,且会短路(every
遇到不满足的元素立即返回false
,some
遇到满足的立即返回true
)。
五、方法对比与最佳实践
5.1 方法选择指南
需求场景 | 推荐方法 | 替代方法 |
---|---|---|
尾部添加元素 | push() | splice(arr.length, 0, x) |
尾部删除元素 | pop() | splice(arr.length-1, 1) |
截取子数组 | slice() | - |
数组映射(转换) | map() | - |
数组过滤 | filter() | - |
累加计算 | reduce() | forEach() (较繁琐) |
查找对象数组元素 | find() /findIndex() | forEach() (需手动判断) |
判断元素是否存在 | includes() | indexOf() !== -1 |
5.2 性能与注意事项
- 修改原数组 vs 返回新数组:
- 需保留原数组时,优先用
slice()
、map()
等不修改原数组的方法; - 频繁操作大型数组时,
push()
比concat()
性能更好(因concat()
返回新数组)。
- 需保留原数组时,优先用
- 遍历中断:
forEach()
无法用break
中断,若需中断可改用for
循环或some()
(通过return true
中断)。
- 引用类型处理:
- 数组方法对对象元素的操作会影响原对象(因对象是引用传递):
const users = [{ name: 'A' }]; users.map(user => { user.age = 18; }); // 修改原对象 console.log(users); // [{ name: 'A', age: 18 }]
六、常见问题与避坑指南
6.1 sort()
的默认排序陷阱
const nums = [10, 2, 23];
nums.sort(); // 错误:按字符串排序,结果[10, 2, 23]
nums.sort((a, b) => a - b); // 正确:数值升序,结果[2, 10, 23]
解决方案:始终传入比较函数处理数值排序。
6.2 indexOf()
无法识别NaN
const arr = [NaN];
console.log(arr.indexOf(NaN)); // -1(错误)
console.log(arr.includes(NaN)); // true(正确)
解决方案:判断NaN
时用includes()
,不用indexOf()
。
6.3 map()
返回新数组的长度不变
const arr = [1, 2, 3];
const newArr = arr.map(num => {if (num > 1) return num * 2;// 未返回值时,默认返回undefined
});
console.log(newArr); // [undefined, 4, 6](长度仍为3)
解决方案:map()
适合“一对一”转换,若需过滤元素应配合filter()
:
const newArr = arr.filter(num => num > 1).map(num => num * 2); // [4, 6]
总结:数组方法的核心要点
- 区分修改与不修改原数组的方法,根据是否需要保留原数据选择;
- 遍历与转换方法(
map()
、filter()
、reduce()
)是处理复杂数据的核心,需熟练掌握;- 查找方法中,
find()
适合对象数组,includes()
适合简单元素判断;- 注意方法的性能差异和特殊场景(如
sort()
的比较函数、includes()
对NaN
的支持)。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ