在编程世界中,数据结构 是组织和操作数据的基础工具。掌握常用的数据结构,不仅能提升代码效率,还能帮助我们更清晰地建模现实问题。本文将结合文档说明与实际示例代码,带你系统学习 JavaScript 中最基础、最常用的线性数据结构------数组(Array) ,并简要介绍其他常见结构,为后续深入学习打下坚实基础。
一、数据结构分类概览
数据结构主要分为两大类:
📏 线性数据结构(Linear Structures)
- 数组(Array) :连续内存,支持随机访问
- 栈(Stack) :后进先出(LIFO / FILO)
- 队列(Queue) :先进先出(FIFO)
- 链表(Linked List) :节点离散存储,通过指针连接
🌲 非线性数据结构(Non-linear Structures)
- 树(Tree) :如二叉树、B树等,具有层次关系
- 图(Graph) :节点与边构成的网络结构(本文暂不展开)
💡 本文重点聚焦于"数组"------JavaScript 中开箱即用、最常用的数据结构。
二、数组:开箱即用的利器
数组是几乎所有编程语言中最基础的数据结构。在 JavaScript 中,数组是动态的、可变长的对象,底层由引擎自动管理内存。
1. 数组的创建方式
javascript
// 方式1:字面量(最常用)
const arr = [1, 2, 3, 4, 5];
// 方式2:构造函数(空数组)
const emptyArr = new Array(); // 或 []
console.log(emptyArr); // []
// 方式3:指定长度(但元素为 empty slots)
const sparseArr = new Array(6);
console.log(sparseArr); // [empty × 6] ------ 注意:这不是 [undefined, undefined, ...]
// 方式4:指定长度 + 初始化值(推荐)
const filledArr = new Array(6).fill(0);
console.log(filledArr); // [0, 0, 0, 0, 0, 0]
⚠️ 注意 :
new Array(n)创建的是一个长度为n的"稀疏数组",其元素并未真正初始化。若需默认值,请务必使用.fill()。
2. 数组的内存特性
- 连续内存 :理想情况下,数组元素在内存中连续存放,因此通过索引访问(
arr[i])的时间复杂度为 O(1) 。 - 动态扩容 :JavaScript 引擎会在数组容量不足时自动扩容(通常策略是翻倍),但这个过程涉及"搬家"------复制旧数据到新内存块,开销较大。
📌 性能权衡:
- 少量数据:数组优于链表(链表每个节点需额外存储指针,且无法缓存友好)
- 频繁插入/删除(尤其头部):链表可能更优
- 但 JS 中原生数组经过高度优化,日常开发中优先使用数组
三、遍历数组的多种方式
遍历是数组最常用的操作。不同方式在性能、可读性、控制力上各有侧重。
✅ 推荐:传统 for 循环(高性能)
ini
const arr = [1, 2, 3, 4, 5];
const len = arr.length; // 缓存 length,避免每次访问属性
for (let i = 0; i < len; i++) {
console.log(arr[i]); // O(1) 访问
}
✅ 优点 :性能最佳,CPU 友好,支持
break/continue❌ 缺点:代码略显冗长
🔄 forEach(简洁但有限制)
perl
arr.forEach((item, index) => {
if (item === 3) {
// ❌ 无法 break!会报错
// break; // SyntaxError
}
console.log(item, index);
});
✅ 优点 :语义清晰,适合纯遍历
❌ 缺点:无法中断;每次调用函数有额外开销(入栈/出栈)
🔁 for...of(ES6 推荐写法)
arduino
for (const item of arr) {
console.log(item);
}
✅ 优点 :可读性强,支持
break/continue,专为可迭代对象设计✅ 适用场景:不需要索引时的首选
🔍 for...in(慎用于数组!)
vbnet
for (const key in arr) {
console.log(key, arr[key]); // key 是字符串 "0", "1", ...
}
⚠️ 警告 :
for...in遍历的是对象的可枚举属性 ,包括原型链上的属性(若被污染)。虽然数组下标是属性名,但不推荐用于数组遍历,应仅用于普通对象。
🛠️ map(用于转换)
c
const newArr = arr.map(item => item + 1);
console.log(newArr); // [2, 3, 4, 5, 6]
✅ 用途 :对每个元素加工并返回新数组
❌ 不是遍历替代品:它会创建新数组,有内存开销
四、实战小贴士:避免常见陷阱
陷阱1:闭包与循环变量
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0, 1, 2 (因为 let 有块级作用域)
}, 100);
}
// 若用 var:
for (var j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // 输出 3, 3, 3 ❌
}, 100);
}
✅ 解决方案 :始终使用
let声明循环变量!
陷阱2:稀疏数组的误解
javascript
const a = new Array(3);
console.log(a[0]); // undefined
console.log(0 in a); // false ------ 索引 0 不存在!
const b = [undefined, undefined, undefined];
console.log(0 in b); // true
✅ 建议 :需要预填充数组时,务必使用
.fill()
五、总结:何时用哪种结构?
| 场景 | 推荐结构 |
|---|---|
| 需要快速随机访问 | ✅ 数组 |
| 只在一端增删(如撤销操作) | ✅ 栈 (可用数组模拟:push/pop) |
| 先进先出(如任务队列) | ✅ 队列 (可用数组模拟:push/shift,但大数据量建议用双端队列) |
| 频繁在中间插入/删除 | ⚠️ 考虑 链表(但 JS 无原生支持,需自行实现) |
结语
数组虽简单,却是理解数据结构的起点。掌握其创建、内存模型、遍历方式及性能特点,能让你写出更高效、更健壮的代码。下一步,你可以探索栈、队列的数组实现 ,或深入链表与树的自定义结构。
🌟 记住 :没有"最好"的数据结构,只有"最合适"的选择。理解每种结构的时间复杂度、空间开销与适用场景,才是高手之道。
Happy Coding! 🚀