栈与队列完全指南 🚀
一、栈(Stack)的世界
1. 什么是栈?
想象一叠盘子 🍽️,你只能从顶部放入或取出盘子。这就是栈的工作原理!
- 后进先出(LIFO: Last In First Out)
- 只能在一端(栈顶)进行操作
2. 栈在前端中的应用
- 浏览器的前进/后退功能
- JavaScript的调用栈
- 括号匹配验证
- 撤销/重做功能
- 路由历史记录
3. 栈的实现
js
class Stack {
#items = []; // 私有属性,外部无法直接访问
// 入栈
push(element) {
this.#items.push(element);
}
// 出栈
pop() {
return this.#items.pop();
}
// 查看栈顶元素
peek() {
return this.#items[this.#items.length - 1];
}
// 检查栈是否为空
isEmpty() {
return this.#items.length === 0;
}
// 获取栈的大小
size() {
return this.#items.length;
}
// 清空栈
clear() {
this.#items = [];
}
}
4. 实战应用示例
括号匹配检查器 🎯
js
function isValidBrackets(str) {
const stack = new Stack();
const brackets = {
'(': ')',
'[': ']',
'{': '}'
};
for (const char of str) {
if (brackets[char]) {
// 遇到左括号,入栈
stack.push(char);
} else {
// 遇到右括号,检查是否匹配
const last = stack.pop();
if (brackets[last] !== char) {
return false;
}
}
}
return stack.isEmpty();
}
// 使用示例
console.log(isValidBrackets('({[]})')); // true
console.log(isValidBrackets('({[})')); // false
二、队列(Queue)的奥秘
1. 什么是队列?
就像排队买奶茶 🧋,先来的人先买,后来的人要排在队尾。这就是队列!
- 先进先出(FIFO: First In First Out)
- 一端进(队尾),另一端出(队头)
2. 队列在前端中的应用
- 事件循环(Event Loop)
- 异步任务队列
- 打印任务队列
- 消息队列
- 请求队列管理
3. 队列的实现
js
class Queue {
#items = {}; // 使用对象实现,性能更好
#firstCount = 0; // 队头指针
#lastCount = 0; // 队尾指针
// 入队
enqueue(element) {
this.#items[this.#lastCount] = element;
this.#lastCount++;
}
// 出队
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.#items[this.#firstCount];
delete this.#items[this.#firstCount];
this.#firstCount++;
return result;
}
// 查看队头元素
front() {
return this.#items[this.#firstCount];
}
// 检查队列是否为空
isEmpty() {
return this.size() === 0;
}
// 获取队列大小
size() {
return this.#lastCount - this.#firstCount;
}
// 清空队列
clear() {
this.#items = {};
this.#firstCount = 0;
this.#lastCount = 0;
}
}
4. 实战应用示例
请求队列管理器 🔄
js
class RequestQueue {
constructor(maxConcurrent = 2) {
this.queue = new Queue();
this.maxConcurrent = maxConcurrent;
this.runningCount = 0;
}
async addRequest(requestFn) {
if (this.runningCount < this.maxConcurrent) {
// 直接执行请求
await this.executeRequest(requestFn);
} else {
// 加入队列等待
this.queue.enqueue(requestFn);
}
}
async executeRequest(requestFn) {
this.runningCount++;
try {
await requestFn();
} finally {
this.runningCount--;
// 检查队列中是否有等待的请求
if (!this.queue.isEmpty()) {
const nextRequest = this.queue.dequeue();
this.executeRequest(nextRequest);
}
}
}
}
// 使用示例
const requestQueue = new RequestQueue(2);
requestQueue.addRequest(() => fetch('api/data1'));
requestQueue.addRequest(() => fetch('api/data2'));
requestQueue.addRequest(() => fetch('api/data3')); // 会进入队列等待
三、性能对比 📊
操作 | 栈 | 队列(数组实现) | 队列(对象实现) |
---|---|---|---|
插入 | O(1) | O(1) | O(1) |
删除 | O(1) | O(n) | O(1) |
查看顶部元素 | O(1) | O(1) | O(1) |
空间占用 | 中等 | 较大 | 较小 |
四、最佳实践建议 💡
选择合适的实现方式
- 小数据量:数组实现简单直观
- 大数据量:对象实现性能更好
注意边界情况
- 空栈/空队列的处理
- 容量限制的考虑
- 类型检查
合理使用TypeScript
typescriptclass Stack<T> { private items: T[] = []; // ... 其他方法 }
五、常见面试题 📝
- 用栈实现队列
- 用队列实现栈
- 实现一个最小栈
- 如何用栈实现浏览器的前进后退功能?