一篇搞定Javascript中的数组,看不懂直接砍作者
JavaScript 数组完全入门指南:从零基础到熟练掌握
适合初学者的数组学习指南,包含详细的概念解释和丰富的代码示例
前言
数组是编程中最基础也是最重要的数据结构之一。想象一下,你需要存储一个班级里所有学生的名字,或者记录一周七天的温度数据——这时候数组就派上用场了。简单来说,数组就是一个可以存放多个数据的容器,这些数据按照顺序排列,每个数据都有自己的"门牌号"(索引)。
在这篇文章中,我会从最基础的概念开始,一步步带你掌握 JavaScript 数组的各种用法。无论你是刚开始学习编程,还是想要系统地复习数组知识,这篇文章都能帮到你。
第一部分:什么是数组?
1.1 生活中的数组
在理解编程中的数组之前,让我们先看看生活中的例子:
- 书架上的书:每本书都有一个位置(第1本、第2本、第3本…)
- 日历上的日期:1号、2号、3号…按顺序排列
- 储物柜:每个柜子都有编号,你可以根据编号找到对应的柜子
数组的原理和这些例子很像:它是一组按顺序排列的数据集合,每个数据都有一个对应的索引(位置编号)。
1.2 数组的特点
JavaScript 的数组有以下几个重要特点:
- 有序性:数组中的元素按照添加的顺序排列
- 索引访问:通过索引(从 0 开始)可以快速访问任意元素
- 类型灵活:一个数组可以同时存放数字、字符串、对象等不同类型的数据
- 长度可变:可以随时添加或删除元素,数组长度会自动调整
1.3 数组的基本结构
// 想象这是一个书架,每个位置放着不同的书
const bookshelf = ['红楼梦', '西游记', '三国演义', '水浒传'];
// 索引: 0 1 2 3
// 对应: 第1本 第2本 第3本 第4本
// 通过索引获取元素
console.log(bookshelf[0]); // 输出:'红楼梦'
console.log(bookshelf[2]); // 输出:'三国演义'
重要提示:数组的索引从 0 开始,而不是 1。这意味着第一个元素的索引是 0,第二个是 1,以此类推。这是编程中的通用约定,刚开始可能会不习惯,但很快就会适应。
第二部分:创建数组的五种方式
在 JavaScript 中,创建数组有多种方法。作为初学者,你需要掌握最常用的几种。
2.1 数组字面量(最常用)
这是最简单、最直观的创建方式,使用方括号 [] 包裹元素,元素之间用逗号分隔。
// 创建一个空数组
const emptyArray = [];
// 创建包含数字的数组
const numbers = [1, 2, 3, 4, 5];
// 创建包含字符串的数组
const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
// 创建混合类型的数组(JavaScript 允许这样做)
const mixed = [1, 'hello', true, null, { name: '张三' }];
// 数组可以嵌套——数组里放数组
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 这就像一个 3x3 的表格,matrix[0][0] 是 1,matrix[1][2] 是 6
为什么推荐这种方式?
- 语法简洁明了
- 阅读性好,一眼就能看出是个数组
- 性能和其他方式没有差别
2.2 Array 构造函数
这是比较传统的创建方式,使用 new Array() 语法。
// 方式一:传入多个参数,每个参数成为数组的一个元素
const arr1 = new Array(1, 2, 3);
console.log(arr1); // 输出:[1, 2, 3]
// 方式二:只传入一个数字参数,表示创建一个指定长度的空数组
const arr2 = new Array(5);
console.log(arr2); // 输出:[empty × 5] —— 一个长度为5的空数组
// ⚠️ 注意这个容易混淆的地方:
const arr3 = new Array(3); // 创建长度为3的空数组
const arr4 = new Array('3'); // 创建包含字符串'3'的数组 ['3']
构造函数的特殊行为解释:
当你只给 Array 构造函数传一个数字时,JavaScript 会认为你想创建一个具有该长度的空数组。但如果传的是其他类型(比如字符串),它就会把那个值当作数组的元素。
由于这个容易混淆的特性,现代开发中更推荐使用数组字面量。
2.3 Array.of() 方法(ES6)
这是 ES6(ECMAScript 2015)引入的新方法,它解决了构造函数的那个"坑"。
// Array.of 总是把参数当作数组元素,不管参数是什么类型
const arr1 = Array.of(3); // [3] —— 包含数字3的数组
const arr2 = Array.of(1, 2, 3); // [1, 2, 3]
const arr3 = Array.of('hello'); // ['hello']
// 对比构造函数的区别
new Array(3); // 创建长度为3的空数组
Array.of(3); // 创建包含数字3的数组
什么时候用 Array.of?
- 当你需要明确创建一个包含特定值的数组时
- 在函数式编程中,配合其他高阶函数使用
2.4 Array.from() 方法(ES6)
这是一个非常强大的方法,可以把类数组对象或可迭代对象转换成真正的数组。
// 1. 把字符串转换成字符数组
const chars = Array.from('hello');
console.log(chars); // 输出:['h', 'e', 'l', 'l', 'o']
// 2. 把 Set 转换成数组(Set 是 ES6 的一种数据结构,自动去重)
const uniqueNums = Array.from(new Set([1, 2, 2, 3, 3, 3]));
console.log(uniqueNums); // 输出:[1, 2, 3]
// 3. 使用映射函数——第二个参数可以对每个元素进行处理
const doubled = Array.from([1, 2, 3], num => num * 2);
console.log(doubled); // 输出:[2, 4, 6]
// 4. 创建指定长度的数组并填充值
const tenOnes = Array.from({ length: 10 }, () => 1);
console.log(tenOnes); // 输出:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
// 5. 创建 0 到 9 的数组
const zeroToNine = Array.from({ length: 10 }, (_, index) => index);
console.log(zeroToNine); // 输出:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array.from 的强大之处:
- 第一个参数可以是任何有
length属性的对象,或者可迭代对象(字符串、Set、Map 等) - 第二个参数是一个映射函数,类似于
map方法,可以在转换的同时处理每个元素
2.5 展开运算符(ES6)
展开运算符 ... 可以把一个数组"展开"成单独的元素,也可以用来复制数组。
// 1. 复制数组
const original = [1, 2, 3];
const copy = [...original];
console.log(copy); // [1, 2, 3]
console.log(copy === original); // false —— 这是两个不同的数组
// 2. 合并多个数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combined = [...arr1, ...arr2, ...arr3];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 3. 在数组中添加新元素
const numbers = [2, 3, 4];
const withMore = [1, ...numbers, 5];
console.log(withMore); // [1, 2, 3, 4, 5]
// 4. 把字符串转成数组(和 Array.from 效果一样)
const chars = [...'hello'];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
展开运算符的优势:
- 语法简洁,可读性好
- 创建的是数组的浅拷贝,不会影响原数组
- 在函数参数中也很常用
第三部分:访问和修改数组元素
3.1 通过索引访问元素
数组的索引从 0 开始,使用中括号 [] 来访问。
const colors = ['红色', '橙色', '黄色', '绿色', '蓝色'];
// 访问元素
console.log(colors[0]); // '红色' —— 第一个元素
console.log(colors[2]); // '黄色' —— 第三个元素
// 访问最后一个元素
console.log(colors[colors.length - 1]); // '蓝色'
// 解释:length 是 5,最后一个元素的索引是 4,所以用 length - 1
// 访问不存在的索引会返回 undefined
console.log(colors[100]); // undefined
console.log(colors[-1]); // undefined(负数索引在普通数组中无效)
3.2 at() 方法(ES2022)
at() 是较新的方法,它支持负数索引,让访问末尾元素更方便。
const colors = ['红色', '橙色', '黄色', '绿色', '蓝色'];
// 正数索引(和普通索引一样)
console.log(colors.at(0)); // '红色'
console.log(colors.at(2)); // '黄色'
// 负数索引(从末尾开始计数)
console.log(colors.at(-1)); // '蓝色' —— 最后一个元素
console.log(colors.at(-2)); // '绿色' —— 倒数第二个元素
console.log(colors.at(-3)); // '黄色' —— 倒数第三个元素
// 对比传统方式
// 传统方式获取最后一个元素
const last = colors[colors.length - 1];
// 使用 at() 获取最后一个元素
const lastWithAt = colors.at(-1);
// 显然 at(-1) 更简洁易懂
3.3 修改数组元素
const scores = [85, 90, 78, 92];
// 修改指定位置的元素
scores[1] = 95;
console.log(scores); // [85, 95, 78, 92]
// 通过索引添加新元素
scores[4] = 88;
console.log(scores); // [85, 95, 78, 92, 88]
// 如果跳过一个索引添加,中间会出现空位(不推荐这样做)
scores[10] = 100;
console.log(scores); // [85, 95, 78, 92, 88, empty × 5, 100]
// 索引 5 到 9 都是空的,这被称为"稀疏数组"
3.4 length 属性
length 属性表示数组的长度(元素个数),它是自动维护的。
const fruits = ['苹果', '香蕉', '橙子'];
console.log(fruits.length); // 3
// 添加元素后,length 自动增加
fruits.push('葡萄');
console.log(fruits.length); // 4
// 手动修改 length 可以截断数组
fruits.length = 2;
console.log(fruits); // ['苹果', '香蕉'] —— 后面的元素被删除了
// 把 length 设为 0 可以快速清空数组
fruits.length = 0;
console.log(fruits); // [] —— 空数组
第四部分:添加和删除元素的方法
数组的强大之处在于可以动态地添加和删除元素。JavaScript 提供了多种方法来完成这些操作。
4.1 尾部操作:push 和 pop
想象数组是一个栈(stack),你只能从顶部放入或取出物品。
const stack = [];
// push —— 在数组末尾添加一个或多个元素
stack.push(1);
console.log(stack); // [1]
stack.push(2, 3); // 可以同时添加多个
console.log(stack); // [1, 2, 3]
// push 返回添加元素后的数组长度
const newLength = stack.push(4);
console.log(newLength); // 4
console.log(stack); // [1, 2, 3, 4]
// pop —— 删除并返回数组的最后一个元素
const lastItem = stack.pop();
console.log(lastItem); // 4
console.log(stack); // [1, 2, 3]
// 对空数组 pop 返回 undefined
const empty = [];
console.log(empty.pop()); // undefined
使用场景:
push:向列表末尾添加新项(如添加新消息到聊天记录)pop:实现"撤销"功能,或处理栈结构的数据
4.2 头部操作:unshift 和 shift
这两个方法和 push/pop 类似,但操作的是数组的开头。
const queue = ['张三', '李四'];
// unshift —— 在数组开头添加一个或多个元素
queue.unshift('王五');
console.log(queue); // ['王五', '张三', '李四']
queue.unshift('赵六', '孙七'); // 添加多个
console.log(queue); // ['赵六', '孙七', '王五', '张三', '李四']
// shift —— 删除并返回数组的第一个元素
const first = queue.shift();
console.log(first); // '赵六'
console.log(queue); // ['孙七', '王五', '张三', '李四']
性能提示:unshift 和 shift 比 push 和 pop 慢,因为它们需要移动数组中所有的现有元素。如果频繁在头部操作,考虑使用其他数据结构(如链表)。
4.3 万能方法:splice
splice 是数组中最强大的方法,可以添加、删除或替换元素。它的名字来源于"拼接"的意思。
const animals = ['狗', '猫', '鸟', '鱼'];
// 语法:array.splice(start, deleteCount, item1, item2, ...)
// start: 开始操作的索引位置
// deleteCount: 要删除的元素数量
// item1, item2, ...: 要添加的新元素(可选)
// 1. 删除元素
const removed = animals.splice(1, 2);
// 从索引1开始,删除2个元素
console.log(removed); // ['猫', '鸟'] —— 被删除的元素
console.log(animals); // ['狗', '鱼'] —— 剩下的元素
// 2. 添加元素(不删除)
animals.splice(1, 0, '兔子', '仓鼠');
// 从索引1开始,删除0个元素,添加'兔子'和'仓鼠'
console.log(animals); // ['狗', '兔子', '仓鼠', '鱼']
// 3. 替换元素
animals.splice(1, 2, '龙猫');
// 从索引1开始,删除2个元素,添加'龙猫'
console.log(animals); // ['狗', '龙猫', '鱼']
splice 的返回值:始终是一个包含被删除元素的数组(如果没有删除元素,返回空数组)。
4.4 截取数组:slice
slice 用于提取数组的一部分,不会改变原数组,而是返回一个新数组。
const fruits = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'];
// 语法:array.slice(start, end)
// start: 开始索引(包含)
// end: 结束索引(不包含),可选,默认到数组末尾
// 提取索引 1 到 3(不包括3)的元素
const citrus = fruits.slice(1, 3);
console.log(citrus); // ['香蕉', '橙子']
console.log(fruits); // 原数组不变:['苹果', '香蕉', '橙子', '葡萄', '西瓜']
// 只传一个参数,从该索引提取到末尾
const lastThree = fruits.slice(2);
console.log(lastThree); // ['橙子', '葡萄', '西瓜']
// 使用负数索引
const lastTwo = fruits.slice(-2);
console.log(lastTwo); // ['葡萄', '西瓜']
// 复制整个数组
const copy = fruits.slice();
console.log(copy); // 原数组的完整副本
slice vs splice:
slice:不修改原数组,返回新数组,用于提取splice:修改原数组,返回被删除的元素,用于增删改
4.5 填充数组:fill
fill 方法用一个固定值填充数组中从起始索引到终止索引内的全部元素。
// 创建长度为5的数组,全部填充为0
const zeros = new Array(5).fill(0);
console.log(zeros); // [0, 0, 0, 0, 0]
// 部分填充
const arr = [1, 2, 3, 4, 5];
arr.fill('x', 1, 3);
// 从索引1开始,到索引3结束(不包括3),填充'x'
console.log(arr); // [1, 'x', 'x', 4, 5]
// 实用技巧:重置数组
const board = ['X', 'O', 'X', ' ', 'O', ' ', ' ', ' ', 'X']; // 井字棋棋盘
board.fill(' '); // 清空棋盘
console.log(board); // [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
第五部分:遍历数组的方法
遍历(iteration)是指依次访问数组中的每个元素。JavaScript 提供了多种遍历方式,各有适用场景。
5.1 for 循环(传统方式)
这是最基础的遍历方式,在所有编程语言中都类似。
const scores = [85, 90, 78, 92, 88];
// 基础写法
for (let i = 0; i < scores.length; i++) {
console.log(`第 ${i + 1} 个学生的成绩是:${scores[i]}`);
}
// 输出:
// 第 1 个学生的成绩是:85
// 第 2 个学生的成绩是:90
// ...
// 优化写法:缓存 length 属性
for (let i = 0, len = scores.length; i < len; i++) {
console.log(scores[i]);
}
// 这样只需要获取一次 length,对于大数组性能更好
// 倒序遍历
for (let i = scores.length - 1; i >= 0; i--) {
console.log(scores[i]);
}
for 循环的优势:
- 性能最好
- 可以控制遍历的步长(如每次跳过一个元素)
- 可以使用
break提前退出,使用continue跳过当前迭代
5.2 for…of 循环(ES6,推荐)
这是 ES6 引入的新语法,专门用于遍历可迭代对象(包括数组)。
const colors = ['红色', '绿色', '蓝色'];
// 直接获取每个元素的值
for (const color of colors) {
console.log(color);
}
// 输出:红色、绿色、蓝色
// 如果需要索引,可以使用 entries()
for (const [index, color] of colors.entries()) {
console.log(`${index}: ${color}`);
}
// 输出:
// 0: 红色
// 1: 绿色
// 2: 蓝色
for…of 的优势:
- 语法简洁,不需要管理索引
- 可以直接获取元素值
- 支持
break和continue
5.3 forEach 方法
forEach 是数组的原生方法,为每个元素执行一次提供的函数。
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(number, index, array) {
console.log(`索引 ${index} 的值是 ${number}`);
});
// 使用箭头函数更简洁
numbers.forEach((number, index) => {
console.log(`${index}: ${number}`);
});
// 如果只关心值,可以省略其他参数
numbers.forEach(num => console.log(num * 2));
forEach 的特点:
- 不能中途退出(不能使用
break或return跳出整个循环) - 无法通过 return 改变循环行为
- 适合对数组每个元素执行相同操作,且不需要中断的场景
5.4 对比三种遍历方式
const arr = [1, 2, 3, 4, 5];
// 场景1:需要提前退出 —— 用 for 或 for...of
for (const num of arr) {
if (num > 3) break; // 找到大于3的就停止
console.log(num); // 输出:1, 2, 3
}
// 场景2:只需要对每个元素执行操作 —— 用 forEach
arr.forEach(num => console.log(num * 2));
// 场景3:需要复杂控制(如每隔一个元素处理一次)—— 用 for
for (let i = 0; i < arr.length; i += 2) {
console.log(arr[i]); // 输出:1, 3, 5
}
第六部分:数组转换和排序
6.1 数组转字符串
const fruits = ['苹果', '香蕉', '橙子'];
// toString —— 默认用逗号连接
console.log(fruits.toString()); // "苹果,香蕉,橙子"
// join —— 可以指定分隔符
console.log(fruits.join()); // "苹果,香蕉,橙子"(默认逗号)
console.log(fruits.join(' - ')); // "苹果 - 香蕉 - 橙子"
console.log(fruits.join('')); // "苹果香蕉橙子"(无分隔符)
console.log(fruits.join(' | ')); // "苹果 | 香蕉 | 橙子"
// 实用技巧:生成 HTML 列表
const html = '<li>' + fruits.join('</li><li>') + '</li>';
console.log(html); // "<li>苹果</li><li>香蕉</li><li>橙子</li>"
6.2 数组排序
sort 方法
sort 方法对数组元素进行排序,会改变原数组。
const letters = ['b', 'a', 'd', 'c'];
letters.sort();
console.log(letters); // ['a', 'b', 'c', 'd'] —— 字母排序正确
// 但是数字排序有陷阱!
const numbers = [10, 2, 30, 1];
numbers.sort();
console.log(numbers); // [1, 10, 2, 30] —— 咦?这不是我们想要的结果!
为什么数字排序不对?
默认情况下,sort 把元素转换成字符串,然后按 Unicode 编码排序。所以 “10” 排在 “2” 前面,因为 “1” 的 Unicode 编码小于 “2”。
正确的数字排序方式:
const numbers = [10, 2, 30, 1];
// 升序排列(从小到大)
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 2, 10, 30]
// 降序排列(从大到小)
numbers.sort((a, b) => b - a);
console.log(numbers); // [30, 10, 2, 1]
比较函数的解释:
(a, b) => a - b:如果结果小于 0,a 排在 b 前面(升序)(a, b) => b - a:如果结果大于 0,a 排在 b 后面(降序)
对象数组排序
const students = [
{ name: '张三', score: 85 },
{ name: '李四', score: 92 },
{ name: '王五', score: 78 }
];
// 按成绩升序
students.sort((a, b) => a.score - b.score);
console.log(students);
// [{name: '王五', score: 78}, {name: '张三', score: 85}, {name: '李四', score: 92}]
// 按姓名排序(字符串比较)
students.sort((a, b) => a.name.localeCompare(b.name));
// localeCompare 是字符串的比较方法,适合中文排序
reverse 方法
reverse 方法反转数组中元素的顺序。
const arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1]
ES2023 新方法(不改变原数组)
const arr = [3, 1, 4, 1, 5];
// toSorted —— 返回排序后的新数组,原数组不变
const sorted = arr.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(arr); // [3, 1, 4, 1, 5] —— 原数组没变
// toReversed —— 返回反转后的新数组
const reversed = arr.toReversed();
console.log(reversed); // [5, 1, 4, 1, 3]
console.log(arr); // [3, 1, 4, 1, 5] —— 原数组没变
6.3 合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
// concat 方法
const combined = arr1.concat(arr2, arr3);
console.log(combined); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2] —— 原数组不变
// 展开运算符(ES6,更推荐)
const combined2 = [...arr1, ...arr2, ...arr3];
console.log(combined2); // [1, 2, 3, 4, 5, 6]
// 还可以在合并时添加新元素
const withExtra = [...arr1, '中间', ...arr2];
console.log(withExtra); // [1, 2, '中间', 3, 4]
第七部分:搜索和查找元素
7.1 基础查找方法
const fruits = ['苹果', '香蕉', '橙子', '香蕉', '葡萄'];
// indexOf —— 查找元素第一次出现的索引
console.log(fruits.indexOf('香蕉')); // 1
console.log(fruits.indexOf('西瓜')); // -1(不存在)
console.log(fruits.indexOf('香蕉', 2)); // 3(从索引2开始查找)
// lastIndexOf —— 查找元素最后一次出现的索引
console.log(fruits.lastIndexOf('香蕉')); // 3
// includes —— 判断是否包含某元素(ES2016)
console.log(fruits.includes('橙子')); // true
console.log(fruits.includes('西瓜')); // false
// includes 可以判断 NaN,indexOf 不行
console.log([NaN].includes(NaN)); // true
console.log([NaN].indexOf(NaN)); // -1(找不到)
7.2 条件查找方法
当需要根据条件查找,而不是精确匹配时,使用这些方法:
const students = [
{ name: '张三', age: 20, passed: true },
{ name: '李四', age: 22, passed: false },
{ name: '王五', age: 21, passed: true }
];
// find —— 返回第一个符合条件的元素
const firstPassed = students.find(s => s.passed);
console.log(firstPassed); // { name: '张三', age: 20, passed: true }
const adult = students.find(s => s.age >= 21);
console.log(adult); // { name: '李四', age: 22, passed: false }
// findIndex —— 返回第一个符合条件的索引
const adultIndex = students.findIndex(s => s.age >= 21);
console.log(adultIndex); // 1
// findLast / findLastIndex —— 从后向前查找(ES2023)
const lastPassed = students.findLast(s => s.passed);
console.log(lastPassed); // { name: '王五', age: 21, passed: true }
// some —— 检查是否有至少一个元素符合条件
const hasFailed = students.some(s => !s.passed);
console.log(hasFailed); // true(有挂科的学生)
// every —— 检查是否所有元素都符合条件
const allAdults = students.every(s => s.age >= 18);
console.log(allAdults); // true(都是成年人)
第八部分:高阶函数(重点掌握)
高阶函数是指接受函数作为参数或返回函数的函数。数组的三个高阶函数 map、filter、reduce 是现代 JavaScript 开发的核心工具,务必熟练掌握。
8.1 map —— 映射转换
map 方法创建一个新数组,其结果是该数组中的每个元素调用一次提供的函数后的返回值。
通俗理解:把数组中的每个元素都"映射"成另一个值,就像给每个人都拍一张照片。
const numbers = [1, 2, 3, 4, 5];
// 把每个数字翻倍
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] —— 原数组不变
// 把每个数字变成它的平方
const squared = numbers.map(num => num ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
// 实际应用:提取对象数组中的某个属性
const users = [
{ id: 1, name: '张三', email: 'zhang@example.com' },
{ id: 2, name: '李四', email: 'li@example.com' },
{ id: 3, name: '王五', email: 'wang@example.com' }
];
const names = users.map(user => user.name);
console.log(names); // ['张三', '李四', '王五']
const userInfo = users.map(user => `${user.name} (${user.email})`);
console.log(userInfo); // ['张三 (zhang@example.com)', '李四 (li@example.com)', ...]
// map 接收三个参数:当前元素、索引、原数组
const withIndex = numbers.map((num, index) => `${index}: ${num}`);
console.log(withIndex); // ['0: 1', '1: 2', '2: 3', '3: 4', '4: 5']
8.2 filter —— 过滤筛选
filter 方法创建一个新数组,包含所有通过测试的元素。
通俗理解:就像用筛子过滤,只保留符合条件的元素。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 筛选出偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// 筛选出大于5的数字
const bigOnes = numbers.filter(num => num > 5);
console.log(bigOnes); // [6, 7, 8, 9, 10]
// 实际应用:筛选活跃用户
const users = [
{ name: '张三', active: true },
{ name: '李四', active: false },
{ name: '王五', active: true }
];
const activeUsers = users.filter(user => user.active);
console.log(activeUsers); // [{name: '张三', active: true}, {name: '王五', active: true}]
// 筛选成年用户
const people = [
{ name: '小明', age: 16 },
{ name: '小红', age: 20 },
{ name: '小刚', age: 25 }
];
const adults = people.filter(person => person.age >= 18);
console.log(adults); // [{name: '小红', age: 20}, {name: '小刚', age: 25}]
// 实用技巧:数组去重(结合 indexOf)
const duplicates = [1, 2, 2, 3, 3, 3, 4];
const unique = duplicates.filter((item, index, arr) => arr.indexOf(item) === index);
console.log(unique); // [1, 2, 3, 4]
// 原理:indexOf 总是返回第一次出现的索引,如果当前索引不等于第一次出现的索引,说明是重复项
8.3 reduce —— 归约汇总
reduce 方法对数组中的每个元素执行一个 reducer 函数,最终汇总为一个值。
通俗理解:把数组中的所有元素"归约"成一个值,就像把一堆东西打包成一个包裹。
const numbers = [1, 2, 3, 4, 5];
// 基本语法:array.reduce((累加器, 当前值) => 新的累加器, 初始值)
// 求和
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// 执行过程:
// 初始: acc = 0
// 第1次: acc = 0 + 1 = 1
// 第2次: acc = 1 + 2 = 3
// 第3次: acc = 3 + 3 = 6
// 第4次: acc = 6 + 4 = 10
// 第5次: acc = 10 + 5 = 15
console.log(sum); // 15
// 求积
const product = numbers.reduce((acc, curr) => acc * curr, 1);
console.log(product); // 120 (1*2*3*4*5)
// 找最大值
const max = numbers.reduce((acc, curr) => Math.max(acc, curr));
console.log(max); // 5
// 实际应用:分组
const people = [
{ name: '张三', department: '技术部' },
{ name: '李四', department: '销售部' },
{ name: '王五', department: '技术部' },
{ name: '赵六', department: '销售部' }
];
const grouped = people.reduce((acc, person) => {
const dept = person.department;
// 如果该部门还不存在,创建一个空数组
if (!acc[dept]) {
acc[dept] = [];
}
// 把当前人员添加到对应部门
acc[dept].push(person);
return acc;
}, {});
console.log(grouped);
// {
// '技术部': [{name: '张三', ...}, {name: '王五', ...}],
// '销售部': [{name: '李四', ...}, {name: '赵六', ...}]
// }
// 实际应用:统计出现次数
const fruits = ['苹果', '香蕉', '苹果', '橙子', '香蕉', '苹果'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // { '苹果': 3, '香蕉': 2, '橙子': 1 }
8.4 链式调用
map、filter、reduce 都返回数组(reduce 返回汇总值),所以可以链式调用:
const students = [
{ name: '张三', score: 85 },
{ name: '李四', score: 92 },
{ name: '王五', score: 78 },
{ name: '赵六', score: 95 }
];
// 需求:找出成绩大于80分的学生,提取他们的名字,然后用逗号连接
const result = students
.filter(s => s.score > 80) // 筛选高分学生
.map(s => s.name) // 提取名字
.join('、'); // 连接成字符串
console.log(result); // "张三、李四、赵六"
// 更复杂的例子:计算平均分
const average = students
.filter(s => s.score >= 60) // 只算及格的学生
.map(s => s.score) // 提取分数
.reduce((sum, score, _, arr) => sum + score / arr.length, 0);
console.log(average); // 及格学生的平均分
第九部分:数组扁平化
9.1 flat 方法
flat 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const nested = [1, [2, 3], [[4, 5], 6]];
// 默认只扁平化一层
console.log(nested.flat()); // [1, 2, 3, [4, 5], 6]
// 指定深度为2
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
// 完全扁平化(不管嵌套多深)
const deep = [1, [2, [3, [4, [5]]]]];
console.log(deep.flat(Infinity)); // [1, 2, 3, 4, 5]
// 自动去除空项
const sparse = [1, , 2, , 3];
console.log(sparse.flat()); // [1, 2, 3]
9.2 flatMap 方法
flatMap 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它等价于 map 后跟着深度为 1 的 flat。
const sentences = ['Hello world', 'Good morning', 'See you'];
// 先 map 分割成单词数组,再 flat 扁平化
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'world', 'Good', 'morning', 'See', 'you']
// 对比分开写
const words2 = sentences.map(s => s.split(' ')).flat();
// 结果相同,但 flatMap 更高效
// 实用技巧:过滤并转换
const numbers = [1, 2, 3, 4, 5, 6];
// 只保留偶数,并把它们翻倍
const result = numbers.flatMap(n => n % 2 === 0 ? [n * 2] : []);
console.log(result); // [4, 8, 12]
// 原理:返回空数组的元素会被 flat 掉,相当于过滤掉了
第十部分:实用技巧和最佳实践
10.1 快速清空数组
const arr = [1, 2, 3, 4, 5];
// 方法1:设置 length 为 0(最快)
arr.length = 0;
// 方法2:使用 splice
arr.splice(0, arr.length);
// 方法3:重新赋值为空数组(如果不需要保持引用)
// arr = []; // 这只在 arr 用 let 声明时有效
10.2 数组去重
const duplicates = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
// 方法1:使用 Set(最简洁,推荐)
const unique1 = [...new Set(duplicates)];
console.log(unique1); // [1, 2, 3, 4]
// 方法2:使用 filter
const unique2 = duplicates.filter((item, index, arr) =>
arr.indexOf(item) === index
);
// 方法3:使用 reduce
const unique3 = duplicates.reduce((acc, curr) =>
acc.includes(curr) ? acc : [...acc, curr]
, []);
10.3 判断数组
// 正确的方式
Array.isArray([]); // true
Array.isArray({}); // false
Array.isArray('string'); // false
Array.isArray(null); // false
// 错误的方式(不要这样用)
typeof []; // 'object' —— 数组也是对象!
[] instanceof Array; // true,但在跨 iframe 时可能失效
10.4 随机打乱数组(Fisher-Yates 算法)
function shuffle(array) {
// 复制数组,避免修改原数组
const arr = [...array];
for (let i = arr.length - 1; i > 0; i--) {
// 生成 0 到 i 的随机索引
const j = Math.floor(Math.random() * (i + 1));
// 交换位置
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
const cards = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
const shuffled = shuffle(cards);
console.log(shuffled); // 随机顺序的牌组
10.5 数组分块
function chunk(array, size) {
return Array.from({ length: Math.ceil(array.length / size) },
(_, index) => array.slice(index * size, index * size + size)
);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(chunk(numbers, 3)); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
console.log(chunk(numbers, 4)); // [[1, 2, 3, 4], [5, 6, 7, 8], [9]]
10.6 获取数组交集、差集
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [4, 5, 6, 7, 8];
// 交集(共同拥有的)
const intersection = arr1.filter(x => arr2.includes(x));
console.log(intersection); // [4, 5]
// 差集(arr1 有但 arr2 没有的)
const difference = arr1.filter(x => !arr2.includes(x));
console.log(difference); // [1, 2, 3]
// 并集(所有的,去重)
const union = [...new Set([...arr1, ...arr2])];
console.log(union); // [1, 2, 3, 4, 5, 6, 7, 8]
总结
在这篇文章中,我们系统地学习了 JavaScript 数组的各个方面:
核心知识点回顾
| 类别 | 关键方法 | 用途 |
|---|---|---|
| 创建数组 | [], Array.of, Array.from, ... |
初始化数据 |
| 增删元素 | push/pop, unshift/shift, splice |
修改数组内容 |
| 访问元素 | [], at() |
读取数据 |
| 遍历数组 | for, for...of, forEach |
处理每个元素 |
| 转换数组 | map, filter, flat |
创建新数组 |
| 汇总数据 | reduce |
归约为单个值 |
| 查找元素 | find, indexOf, includes |
定位数据 |
| 排序 | sort, reverse |
调整顺序 |
给初学者的建议
- 从基础开始:先掌握数组字面量创建、索引访问、push/pop 这些基础操作
- 重点练习高阶函数:
map、filter、reduce是现代 JavaScript 的核心,务必熟练掌握 - 多写代码:数组方法很多,只有通过实践才能真正记住和理解
- 注意不变性:优先使用不改变原数组的方法(如
map而不是forEach修改),这样代码更易于理解和维护 - 查阅文档:遇到不确定的方法,及时查阅 MDN 文档
练习建议
- 尝试用
map、filter、reduce重写你之前用for循环写的代码 - 实现一个简单的待办事项列表,练习增删改查操作
- 处理一些真实数据(如从 API 获取的用户列表),进行筛选、排序、分组等操作
希望这篇文章能帮助你全面掌握 JavaScript 数组!如果有任何疑问,欢迎继续探讨。
更多推荐
所有评论(0)