数组基础:管理数据的有序容器
你走进一家图书馆,会发现书籍并不是随意堆放的。每本书都有固定的位置,通过书架编号和层数就能快速找到。第一层是科幻小说,第二层是历史书籍,第三层是技术类书籍。这种有序的组织方式让查找和管理变得简单高效。在编程中,数组就扮演着这样的角色——它是一个有序的容器,让我们能够系统地存储和访问多个相关的数据。
什么是数组
数组(Array)是 JavaScript 中最基础也是最常用的数据结构之一。它可以在一个变量中存储多个值,这些值按照特定的顺序排列,每个值都有一个数字索引(从 0 开始)来标识它的位置。
想象你需要管理一个班级的学生名单。如果没有数组,你可能需要这样写:
let student1 = "Alice";
let student2 = "Bob";
let student3 = "Charlie";
let student4 = "David";
let student5 = "Emma";这种方式既繁琐又不便于管理。如果班级有 30 个学生呢?有了数组,事情变得简单多了:
let students = ["Alice", "Bob", "Charlie", "David", "Emma"];现在所有学生的名字都整齐地存储在一个数组中。数组的魅力在于它的有序性和可访问性。第一个元素总是在索引 0 的位置,第二个在索引 1,以此类推。
console.log(students[0]); // "Alice" - 索引 0 是第一个元素
console.log(students[1]); // "Bob" - 索引 1 是第二个元素
console.log(students[4]); // "Emma" - 索引 4 是第五个元素创建数组的多种方式
JavaScript 提供了多种创建数组的方法,每种方法都有其适用场景。
数组字面量(最常用)
使用方括号 [] 创建数组是最直观、最常用的方式:
// 创建空数组
let emptyArray = [];
// 创建包含数字的数组
let numbers = [1, 2, 3, 4, 5];
// 创建包含字符串的数组
let fruits = ["apple", "banana", "orange"];
// 数组可以包含不同类型的值
let mixed = [1, "hello", true, null, { name: "John" }];
// 数组可以包含表达式
let calculated = [1 + 1, 2 * 3, 10 / 2]; // [2, 6, 5]这种方式简洁明了,一眼就能看出数组包含哪些元素。在实际开发中,这是创建数组的首选方法。
Array 构造函数
使用 new Array() 构造函数也可以创建数组,但需要注意一些细节:
// 创建空数组
let arr1 = new Array();
console.log(arr1); // []
// 创建指定长度的空数组
let arr2 = new Array(5);
console.log(arr2); // [empty × 5] - 5个空槽
console.log(arr2.length); // 5
// 创建包含元素的数组
let arr3 = new Array(1, 2, 3);
console.log(arr3); // [1, 2, 3]
// ⚠️ 注意:单个数字参数会被当作长度
let arr4 = new Array(5); // 创建长度为5的空数组
let arr5 = new Array("5"); // 创建包含字符串"5"的数组使用构造函数时要特别小心。如果只传一个数字参数,它会被解释为数组长度而不是数组元素。这常常导致混淆,所以在大多数情况下,使用字面量语法更安全。
Array.of() 方法
Array.of() 是 ES6 引入的方法,它解决了 Array() 构造函数的歧义问题:
// Array.of() 总是创建包含参数的数组
let arr1 = Array.of(5);
console.log(arr1); // [5] - 包含数字5
let arr2 = Array.of(1, 2, 3, 4, 5);
console.log(arr2); // [1, 2, 3, 4, 5]
// 对比之下,Array构造函数的行为不同
let arr3 = new Array(5);
console.log(arr3); // [empty × 5] - 长度为5的空数组
let arr4 = Array.of();
console.log(arr4); // [] - 空数组Array.of() 的行为更加一致和可预测,特别是在需要动态创建包含单个元素的数组时非常有用。
Array.from() 方法
Array.from() 可以从类数组对象或可迭代对象创建新数组,这是一个功能强大的方法:
// 从字符串创建数组
let str = "hello";
let chars = Array.from(str);
console.log(chars); // ["h", "e", "l", "l", "o"]
// 从 Set 创建数组
let numberSet = new Set([1, 2, 3, 3, 4]);
let uniqueNumbers = Array.from(numberSet);
console.log(uniqueNumbers); // [1, 2, 3, 4]
// 使用映射函数
let doubled = Array.from([1, 2, 3], (x) => x * 2);
console.log(doubled); // [2, 4, 6]
// 创建指定长度的数组并初始化
let sequence = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(sequence); // [1, 2, 3, 4, 5]
// 从类数组对象创建
function getArguments() {
return Array.from(arguments);
}
let args = getArguments(1, 2, 3);
console.log(args); // [1, 2, 3]Array.from() 特别适合转换数据结构和创建初始化的数组序列。第二个参数是可选的映射函数,可以在创建数组的同时对每个元素进行转换。
访问和修改数组元素
数组通过索引来访问和修改元素。索引是从 0 开始的整数。
通过索引访问
let colors = ["red", "green", "blue", "yellow"];
// 访问元素
console.log(colors[0]); // "red"
console.log(colors[2]); // "blue"
// 访问最后一个元素
console.log(colors[colors.length - 1]); // "yellow"
// 访问不存在的索引返回 undefined
console.log(colors[10]); // undefined
// 负数索引在标准数组中不工作(返回undefined)
console.log(colors[-1]); // undefined在 JavaScript 中,数组索引必须是非负整数。访问超出范围的索引会返回 undefined,而不会抛出错误。这是一个需要注意的地方,因为它可能掩盖潜在的逻辑错误。
修改数组元素
let fruits = ["apple", "banana", "orange"];
// 修改现有元素
fruits[1] = "grape";
console.log(fruits); // ["apple", "grape", "orange"]
// 可以在任意索引位置赋值(会创建稀疏数组)
fruits[5] = "mango";
console.log(fruits); // ["apple", "grape", "orange", empty × 2, "mango"]
console.log(fruits.length); // 6
// 中间的空槽
console.log(fruits[3]); // undefined
console.log(fruits[4]); // undefined当你给超出当前长度的索引赋值时,数组会自动扩展,中间的位置会是空槽(empty slots)。这些空槽在遍历时可能会有特殊行为,通常应该避免创建稀疏数组。
length 属性
length 属性表示数组的长度,但它不仅仅是只读的:
let numbers = [1, 2, 3, 4, 5];
// 读取长度
console.log(numbers.length); // 5
// 通过修改length可以截断数组
numbers.length = 3;
console.log(numbers); // [1, 2, 3]
// 增加length会添加空槽
numbers.length = 5;
console.log(numbers); // [1, 2, 3, empty × 2]
// 设置为0可以清空数组
numbers.length = 0;
console.log(numbers); // []修改 length 属性是一种快速截断或清空数组的方法。如果减小 length,超出新长度的元素会被永久删除。如果增加 length,新增的位置会是空槽。
多维数组
数组可以包含其他数组作为元素,这样就形成了多维数组。最常见的是二维数组,就像表格或矩阵一样。
创建二维数组
// 创建一个简单的二维数组(3x3矩阵)
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
// 访问元素
console.log(matrix[0][0]); // 1 - 第一行第一列
console.log(matrix[1][2]); // 6 - 第二行第三列
console.log(matrix[2][1]); // 8 - 第三行第二列
// 修改元素
matrix[1][1] = 50;
console.log(matrix);
// [
// [1, 2, 3],
// [4, 50, 6],
// [7, 8, 9]
// ]二维数组的访问使用两个索引:第一个索引选择"行",第二个索引选择"列"。这种结构非常适合表示表格数据、游戏棋盘、图像像素等。
实际应用:学生成绩表
// 使用二维数组存储学生成绩
let grades = [
["Alice", 85, 90, 88],
["Bob", 78, 82, 85],
["Charlie", 92, 88, 95],
];
// 访问 Bob 的第二门课成绩
console.log(grades[1][2]); // 82
// 计算 Alice 的平均分
let aliceScores = grades[0].slice(1); // [85, 90, 88]
let average =
aliceScores.reduce((sum, score) => sum + score, 0) / aliceScores.length;
console.log(average); // 87.67
// 遍历所有学生的成绩
grades.forEach((student) => {
let name = student[0];
let scores = student.slice(1);
let avg = scores.reduce((a, b) => a + b) / scores.length;
console.log(`${name}: ${avg.toFixed(2)}`);
});
// Alice: 87.67
// Bob: 81.67
// Charlie: 91.67更高维度的数组
虽然不太常见,但你可以创建三维甚至更高维度的数组:
// 三维数组:可以想象成多个二维矩阵叠在一起
let cube = [
[
[1, 2],
[3, 4],
],
[
[5, 6],
[7, 8],
],
];
console.log(cube[0][1][0]); // 3
console.log(cube[1][0][1]); // 6
// 实际应用:RGB 图像数据(宽 x 高 x 颜色通道)
let imageData = [
[
[255, 0, 0],
[0, 255, 0],
], // 第一行:红色、绿色
[
[0, 0, 255],
[255, 255, 0],
], // 第二行:蓝色、黄色
];
// 获取第一行第二个像素的绿色通道值
console.log(imageData[0][1][1]); // 255数组的动态特性
JavaScript 数组的一个重要特点是它们的大小是动态的。你可以随时添加或删除元素,数组会自动调整大小。
let items = ["book"];
console.log(items.length); // 1
// 添加元素
items[1] = "pen";
items[2] = "notebook";
console.log(items.length); // 3
// 数组可以存储不同类型的数据
items[3] = 42;
items[4] = { type: "eraser" };
items[5] = function () {
return "pencil";
};
console.log(items);
// ["book", "pen", "notebook", 42, { type: "eraser" }, function]这种灵活性使得 JavaScript 数组非常强大,但也需要你在使用时保持一致性,避免在同一个数组中混合过多不同类型的数据。
实际应用场景
1. 管理待办事项列表
let todoList = [
"Buy groceries",
"Finish project report",
"Call the dentist",
"Exercise for 30 minutes",
];
// 显示所有待办事项
console.log("Todo List:");
todoList.forEach((task, index) => {
console.log(`${index + 1}. ${task}`);
});
// 1. Buy groceries
// 2. Finish project report
// 3. Call the dentist
// 4. Exercise for 30 minutes
// 检查是否有特定任务
let hasExercise = todoList.includes("Exercise for 30 minutes");
console.log(`Need to exercise: ${hasExercise}`); // true2. 存储和操作产品库存
let inventory = [
{ id: 1, name: "Laptop", quantity: 15, price: 999 },
{ id: 2, name: "Mouse", quantity: 50, price: 25 },
{ id: 3, name: "Keyboard", quantity: 30, price: 75 },
{ id: 4, name: "Monitor", quantity: 20, price: 299 },
];
// 查找特定产品
let laptop = inventory.find((item) => item.name === "Laptop");
console.log(laptop); // { id: 1, name: "Laptop", quantity: 15, price: 999 }
// 计算总库存价值
let totalValue = inventory.reduce((sum, item) => {
return sum + item.quantity * item.price;
}, 0);
console.log(`Total inventory value: $${totalValue}`); // $36,435
// 找出需要补货的商品(数量少于25)
let lowStock = inventory.filter((item) => item.quantity < 25);
console.log("Low stock items:", lowStock);
// [{ id: 1, name: "Laptop", quantity: 15, price: 999 },
// { id: 4, name: "Monitor", quantity: 20, price: 299 }]3. 处理用户评分数据
let ratings = [4.5, 3.8, 5.0, 4.2, 3.5, 4.8, 4.0, 4.6];
// 计算平均评分
let averageRating =
ratings.reduce((sum, rating) => sum + rating, 0) / ratings.length;
console.log(`Average rating: ${averageRating.toFixed(2)}`); // 4.30
// 找出最高和最低评分
let highest = Math.max(...ratings);
let lowest = Math.min(...ratings);
console.log(`Highest: ${highest}, Lowest: ${lowest}`); // 5.0, 3.5
// 统计不同评分等级的数量
let excellent = ratings.filter((r) => r >= 4.5).length;
let good = ratings.filter((r) => r >= 4.0 && r < 4.5).length;
let average = ratings.filter((r) => r >= 3.5 && r < 4.0).length;
console.log(`Excellent (4.5+): ${excellent}`); // 3
console.log(`Good (4.0-4.4): ${good}`); // 3
console.log(`Average (3.5-3.9): ${average}`); // 24. 创建简单的游戏棋盘
// 创建一个井字棋棋盘
function createBoard() {
return [
[" ", " ", " "],
[" ", " ", " "],
[" ", " ", " "],
];
}
let board = createBoard();
// 在棋盘上放置棋子
function placeMarker(board, row, col, marker) {
if (board[row][col] === " ") {
board[row][col] = marker;
return true;
}
return false;
}
// 显示棋盘
function displayBoard(board) {
console.log("\n");
board.forEach((row, index) => {
console.log(` ${row[0]} | ${row[1]} | ${row[2]} `);
if (index < 2) console.log("---|---|---");
});
console.log("\n");
}
// 玩游戏
placeMarker(board, 0, 0, "X");
placeMarker(board, 1, 1, "O");
placeMarker(board, 0, 2, "X");
displayBoard(board);
// X | | X
// ---|---|---
// | O |
// ---|---|---
// | |常见陷阱与注意事项
1. 数组是引用类型
数组是对象,变量存储的是引用而不是值本身。这意味着赋值和比较时需要特别注意:
let original = [1, 2, 3];
let reference = original; // 复制引用,不是复制数组
reference.push(4);
console.log(original); // [1, 2, 3, 4] - 原数组也被修改了
// 正确的复制方式
let copy1 = [...original]; // 使用展开运算符
let copy2 = original.slice(); // 使用slice方法
let copy3 = Array.from(original); // 使用Array.from
copy1.push(5);
console.log(original); // [1, 2, 3, 4] - 不受影响
console.log(copy1); // [1, 2, 3, 4, 5]
// 数组比较
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false - 比较的是引用,不是内容2. 空数组的真值
空数组在布尔上下文中被视为 true,这可能有些反直觉:
let emptyArray = [];
if (emptyArray) {
console.log("This will print!"); // 会执行
}
// 正确的检查方式
if (emptyArray.length > 0) {
console.log("Array has elements");
} else {
console.log("Array is empty"); // 会执行这里
}3. 稀疏数组的行为
稀疏数组(包含空槽的数组)在某些方法中的行为可能不符合预期:
let sparse = [1, , 3]; // 中间有一个空槽
console.log(sparse.length); // 3
// forEach 会跳过空槽
sparse.forEach((val, i) => {
console.log(`Index ${i}: ${val}`);
});
// Index 0: 1
// Index 2: 3
// 索引1被跳过了
// map 也会跳过空槽
let doubled = sparse.map((x) => x * 2);
console.log(doubled); // [2, empty, 6]
// 但是 for 循环不会跳过
for (let i = 0; i < sparse.length; i++) {
console.log(`Index ${i}: ${sparse[i]}`);
}
// Index 0: 1
// Index 1: undefined
// Index 2: 3总结
数组是 JavaScript 中最基础也是最重要的数据结构。它提供了一种有序的方式来存储和管理多个值。我们学习了:
- 数组的本质 - 有序的数据容器,通过数字索引访问元素
- 创建数组 - 字面量、构造函数、
Array.of()、Array.from()等多种方式 - 访问和修改 - 使用索引访问元素,
length属性管理数组大小 - 多维数组 - 数组嵌套,适合表示表格、矩阵等结构化数据
- 实际应用 - 待办列表、库存管理、评分统计、游戏开发等
- 注意事项 - 引用类型特性、空数组真值、稀疏数组行为
掌握数组基础是学习 JavaScript 的关键一步。在接下来的章节中,我们将深入探讨数组的各种方法,包括如何添加、删除、搜索和转换数组元素。数组方法是 JavaScript 编程的核心工具,能够让你高效地处理和操作数据。