JavaScript数据结构深度解析:栈、队列与树的实现与应用
2026年2月9日 17:53
当函数调用层层嵌套,JavaScript 引擎如何管理这些调用?当事件循环处理异步任务时,背后的数据结构是什么?本文将深入探讨栈、队列和树在前端开发中的核心应用。
前言:从函数调用说起
// 一个简单的函数调用栈示例
function funcA() {
console.log('进入函数A');
funcB();
console.log('离开函数A');
}
function funcB() {
console.log('进入函数B');
funcC();
console.log('离开函数B');
}
function funcC() {
console.log('进入函数C');
console.log('离开函数C');
}
funcA();
这段代码的输出顺序是什么?进入函数A 进入函数B 进入函数C 离开函数C 离开函数B 离开函数A 。
这种"先进后出"的执行顺序,正是栈的典型特征。
栈(Stack)的实现与应用
栈的基本概念
栈 是一种后进先出(LIFO)的数据结构,就像一摞盘子,我们只能从最上面取放盘子。
栈的操作示意图
push(3) push(5) pop() → 5
┌───┐ → ┌───┐ → ┌───┐ → ┌───┐
│ │ │ 3 │ │ 5 │ │ 3 │
├───┤ ├───┤ ├───┤ ├───┤
│ │ │ │ │ 3 │ │ │
└───┘ └───┘ └───┘ └───┘
空栈 栈底:3 栈顶:5 栈底:3
用数组实现栈
class ArrayStack {
constructor(capacity = 10) {
this.items = new Array(capacity); // 底层数组
this.top = -1; // 栈顶指针
this.capacity = capacity; // 栈容量
}
// 入栈操作
push(element) {
if (this.isFull()) {
throw new Error('栈已满');
}
this.top++;
this.items[this.top] = element;
return this;
}
// 出栈操作
pop() {
if (this.isEmpty()) {
throw new Error('栈为空');
}
const element = this.items[this.top];
this.items[this.top] = undefined; // 清理引用
this.top--;
return element;
}
// 查看栈顶元素(不弹出)
peek() {
if (this.isEmpty()) {
return null;
}
return this.items[this.top];
}
// 判断栈是否为空
isEmpty() {
return this.top === -1;
}
// 判断栈是否已满
isFull() {
return this.top === this.capacity - 1;
}
// 获取栈大小
get size() {
return this.top + 1;
}
// 清空栈
clear() {
this.items = new Array(this.capacity);
this.top = -1;
}
}
用链表实现栈
class ListNode {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedListStack {
constructor() {
this.top = null; // 栈顶节点
this.length = 0; // 栈长度
}
// 入栈操作
push(element) {
// 创建新节点,指向原来的栈顶
const newNode = new ListNode(element, this.top);
this.top = newNode;
this.length++;
return this;
}
// 出栈操作
pop() {
if (this.isEmpty()) {
throw new Error('栈为空');
}
const value = this.top.value;
this.top = this.top.next; // 移动栈顶指针
this.length--;
return value;
}
peek() {
if (this.isEmpty()) {
return null;
}
return this.top.value;
}
isEmpty() {
return this.top === null;
}
get size() {
return this.length;
}
clear() {
this.top = null;
this.length = 0;
}
}
数组栈 vs 链表栈
- 数组栈:内存连续,CPU缓存友好,访问速度快
- 链表栈:动态扩容,无容量限制,但内存不连续
- 实际选择:JavaScript 数组经过V8引擎优化,通常数组栈性能更好
栈的实际应用
应用1:函数调用栈模拟
class CallStackSimulator {
constructor() {
this.callStack = new ArrayStack(50);
this.currentDepth = 0;
this.maxDepth = 0;
}
// 函数调用
callFunction(funcName) {
const frame = {
funcName,
timestamp: Date.now(),
localVars: {}
};
this.callStack.push(frame);
this.currentDepth++;
this.maxDepth = Math.max(this.maxDepth, this.currentDepth);
console.log(`调用: ${funcName} (深度: ${this.currentDepth})`);
this.printStack();
return frame;
}
// 函数返回
returnFromFunction() {
if (this.callStack.isEmpty()) {
console.log('调用栈已空');
return null;
}
const frame = this.callStack.pop();
const duration = Date.now() - frame.timestamp;
this.currentDepth--;
console.log(`返回: ${frame.funcName} (耗时: ${duration}ms)`);
console.log(`剩余深度: ${this.currentDepth}`);
return frame;
}
}
队列(Queue)与双端队列(Deque)
队列(Queue)的基本概念
队列 是一种先进先出(FIFO)的数据结构,就像排队买票,先来的人先服务。
队列的操作示意图
enqueue(3) enqueue(5) dequeue() → 3
┌───┬───┐ → ┌───┬───┐ → ┌───┬───┐ → ┌───┬───┐
│ │ │ │ 3 │ │ │ 3 │ 5 │ │ │ 5 │
└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘
front rear front↑rear front rear↑ ↑front rear
双端队列(Deque)的基本概念
双端队列 允许在两端进行插入和删除操作,结合了栈和队列的特性。
双端队列的实现
class Deque {
constructor(capacity = 10) {
this.items = new Array(capacity);
this.capacity = capacity;
this.front = 0;
this.rear = 0;
this.count = 0;
}
// 前端添加
addFront(element) {
if (this.isFull()) {
this._resize();
}
this.front = (this.front - 1 + this.capacity) % this.capacity;
this.items[this.front] = element;
this.count++;
console.log(`前端添加: ${element}, front: ${this.front}`);
return this;
}
// 后端添加
addRear(element) {
if (this.isFull()) {
this._resize();
}
this.items[this.rear] = element;
this.rear = (this.rear + 1) % this.capacity;
this.count++;
console.log(`后端添加: ${element}, rear: ${this.rear}`);
return this;
}
// 前端删除
removeFront() {
if (this.isEmpty()) {
throw new Error('队列为空');
}
const element = this.items[this.front];
this.items[this.front] = undefined;
this.front = (this.front + 1) % this.capacity;
this.count--;
console.log(`前端删除: ${element}, front: ${this.front}`);
return element;
}
// 后端删除
removeRear() {
if (this.isEmpty()) {
throw new Error('队列为空');
}
this.rear = (this.rear - 1 + this.capacity) % this.capacity;
const element = this.items[this.rear];
this.items[this.rear] = undefined;
this.count--;
console.log(`后端删除: ${element}, rear: ${this.rear}`);
return element;
}
// 查看前端
peekFront() {
if (this.isEmpty()) {
return null;
}
return this.items[this.front];
}
// 查看后端
peekRear() {
if (this.isEmpty()) {
return null;
}
const index = (this.rear - 1 + this.capacity) % this.capacity;
return this.items[index];
}
// 扩容
_resize() {
const newCapacity = this.capacity * 2;
const newItems = new Array(newCapacity);
// 复制元素到新数组
for (let i = 0; i < this.count; i++) {
const index = (this.front + i) % this.capacity;
newItems[i] = this.items[index];
}
this.items = newItems;
this.front = 0;
this.rear = this.count;
this.capacity = newCapacity;
console.log(`队列扩容: ${this.capacity / 2} → ${newCapacity}`);
}
isEmpty() {
return this.count === 0;
}
isFull() {
return this.count === this.capacity;
}
get size() {
return this.count;
}
}
队列的实际应用
应用1:任务调度器
class TaskScheduler {
constructor() {
this.taskQueue = new CircularQueue(100); // 任务队列
this.currentTask = null;
this.taskId = 0;
this.isProcessing = false;
}
// 创建任务
createTask(name, duration, priority = 0) {
return {
id: ++this.taskId,
name,
duration, // 任务执行时间(毫秒)
priority, // 优先级(数值越小优先级越高)
status: 'pending',
createdAt: Date.now(),
startedAt: null,
completedAt: null
};
}
// 添加任务到队列
addTask(task) {
this.taskQueue.enqueue(task);
console.log(`任务添加: ${task.name} (ID: ${task.id})`);
this.printQueueStatus();
// 如果没有正在处理的任务,开始处理
if (!this.isProcessing) {
this.processNextTask();
}
}
// 处理下一个任务
async processNextTask() {
if (this.taskQueue.isEmpty()) {
this.isProcessing = false;
console.log('所有任务处理完成!');
return;
}
this.isProcessing = true;
this.currentTask = this.taskQueue.dequeue();
this.currentTask.status = 'processing';
this.currentTask.startedAt = Date.now();
console.log(`\n开始处理任务: ${this.currentTask.name}`);
console.log(`任务ID: ${this.currentTask.id}, 预计耗时: ${this.currentTask.duration}ms`);
this.printQueueStatus();
// 模拟任务执行
await this.executeTask(this.currentTask);
// 任务完成
this.currentTask.completedAt = Date.now();
this.currentTask.status = 'completed';
const totalTime = this.currentTask.completedAt - this.currentTask.startedAt;
console.log(`任务完成: ${this.currentTask.name}`);
console.log(`实际耗时: ${totalTime}ms`);
// 处理下一个任务
setTimeout(() => {
this.processNextTask();
}, 0);
}
// 模拟任务执行
executeTask(task) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, task.duration);
});
}
}
树(Tree)的实现与应用
树的基本概念
树是一种分层的数据结构,由节点和边组成。每个节点有零个或多个子节点,没有父节点的节点称为根节点;没有子节点的节点称为叶子节点。
二叉树结构示意图
A (根节点)
/ \
B C
/ \ \
D E F
/
G
术语解释:
- 根节点: A
- 叶子节点: E, F, G
- 深度: 节点G的深度是3
- 高度: 树的高度是3
- 子树: B、D、E、G构成一个子树
二叉树的实现
class TreeNode {
constructor(value) {
this.value = value; // 节点值
this.left = null; // 左子节点
this.right = null; // 右子节点
this.parent = null; // 父节点(可选)
}
// 判断是否为叶子节点
isLeaf() {
return this.left === null && this.right === null;
}
// 判断是否有左子节点
hasLeft() {
return this.left !== null;
}
// 判断是否有右子节点
hasRight() {
return this.right !== null;
}
// 获取高度
getHeight() {
const leftHeight = this.left ? this.left.getHeight() : 0;
const rightHeight = this.right ? this.right.getHeight() : 0;
return Math.max(leftHeight, rightHeight) + 1;
}
}
二叉树遍历算法
class BinaryTree {
constructor() {
this.root = null; // 根节点
}
// 前序遍历:根 → 左 → 右
preorderTraversal(callback) {
const result = [];
this._preorder(this.root, callback, result);
return result;
}
_preorder(node, callback, result) {
if (node === null) return;
// 访问当前节点
if (callback) callback(node);
result.push(node.value);
// 遍历左子树
this._preorder(node.left, callback, result);
// 遍历右子树
this._preorder(node.right, callback, result);
}
// 中序遍历:左 → 根 → 右(对二叉搜索树会得到有序序列)
inorderTraversal(callback) {
const result = [];
this._inorder(this.root, callback, result);
return result;
}
_inorder(node, callback, result) {
if (node === null) return;
// 遍历左子树
this._inorder(node.left, callback, result);
// 访问当前节点
if (callback) callback(node);
result.push(node.value);
// 遍历右子树
this._inorder(node.right, callback, result);
}
// 后序遍历:左 → 右 → 根
postorderTraversal(callback) {
const result = [];
this._postorder(this.root, callback, result);
return result;
}
_postorder(node, callback, result) {
if (node === null) return;
// 遍历左子树
this._postorder(node.left, callback, result);
// 遍历右子树
this._postorder(node.right, callback, result);
// 访问当前节点
if (callback) callback(node);
result.push(node.value);
}
// 层序遍历:按层级从上到下,从左到右
levelOrderTraversal(callback) {
if (this.root === null) return [];
const result = [];
const queue = new CircularQueue(100);
queue.enqueue(this.root);
while (!queue.isEmpty()) {
const node = queue.dequeue();
// 访问当前节点
if (callback) callback(node);
result.push(node.value);
// 将子节点加入队列
if (node.left) {
queue.enqueue(node.left);
}
if (node.right) {
queue.enqueue(node.right);
}
}
return result;
}
// 深度优先搜索(DFS)
dfs(targetValue) {
return this._dfs(this.root, targetValue);
}
_dfs(node, targetValue) {
if (node === null) return null;
// 检查当前节点
if (node.value === targetValue) {
return node;
}
// 搜索左子树
const leftResult = this._dfs(node.left, targetValue);
if (leftResult !== null) {
return leftResult;
}
// 搜索右子树
return this._dfs(node.right, targetValue);
}
// 广度优先搜索(BFS)
bfs(targetValue) {
if (this.root === null) return null;
const queue = new CircularQueue(100);
queue.enqueue(this.root);
while (!queue.isEmpty()) {
const node = queue.dequeue();
// 检查当前节点
if (node.value === targetValue) {
return node;
}
// 将子节点加入队列
if (node.left) {
queue.enqueue(node.left);
}
if (node.right) {
queue.enqueue(node.right);
}
}
return null;
}
}
数据结构选择的关键因素
访问模式
- 随机访问:数组(O(1))
- 顺序访问:链表、栈、队列
- 层级访问:树
操作频率
- 频繁插入/删除开头:链表
- 频繁插入/删除两端:双端队列
- 频繁随机访问:数组
内存考虑
- 内存连续:数组(缓存友好)
- 内存分散:链表(无扩容成本)
- 动态大小:链表、树
结语
本文讲解了栈、队列和树的实现与应用,理解这些数据结构,不仅能帮助我们在面试中表现出色,更能让我们在实际开发中写出更高效、更可靠的代码。对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!