栈与队列(实战篇) - 真实场景应用
在基础篇和进阶篇中,我们学习了栈和队列的原理与技巧。本篇将通过 7 个真实的前端场景,展示如何在实际项目中应用这些数据结构。
一、浏览器历史记录系统
需求分析
实现一个完整的浏览器历史记录系统,支持:
- 访问新页面
- 后退(可指定步数)
- 前进(可指定步数)
- 清除前进历史
- 获取历史记录列表
完整实现
/**
* 浏览器历史记录管理器
*/
class BrowserHistory {
private backStack: string[] = []; // 后退栈
private forwardStack: string[] = []; // 前进栈
private currentPage: string; // 当前页面
private maxHistory: number; // 最大历史记录数
constructor(homepage: string, maxHistory: number = 50) {
this.currentPage = homepage;
this.maxHistory = maxHistory;
}
/**
* 访问新页面
*/
visit(url: string): void {
// 将当前页面加入后退栈
this.backStack.push(this.currentPage);
// 限制历史记录数量
if (this.backStack.length > this.maxHistory) {
this.backStack.shift();
}
// 更新当前页面
this.currentPage = url;
// 清空前进栈(访问新页面后不能前进)
this.forwardStack = [];
console.log(`📄 访问: ${url}`);
}
/**
* 后退指定步数
*/
back(steps: number = 1): string {
let actualSteps = 0;
while (steps > 0 && this.backStack.length > 0) {
// 将当前页面加入前进栈
this.forwardStack.push(this.currentPage);
// 从后退栈取出页面
this.currentPage = this.backStack.pop()!;
steps--;
actualSteps++;
}
console.log(`⬅️ 后退 ${actualSteps} 步: ${this.currentPage}`);
return this.currentPage;
}
/**
* 前进指定步数
*/
forward(steps: number = 1): string {
let actualSteps = 0;
while (steps > 0 && this.forwardStack.length > 0) {
// 将当前页面加入后退栈
this.backStack.push(this.currentPage);
// 从前进栈取出页面
this.currentPage = this.forwardStack.pop()!;
steps--;
actualSteps++;
}
console.log(`➡️ 前进 ${actualSteps} 步: ${this.currentPage}`);
return this.currentPage;
}
/**
* 获取当前页面
*/
getCurrentPage(): string {
return this.currentPage;
}
/**
* 获取历史记录列表
*/
getHistory(): {
back: string[];
current: string;
forward: string[];
} {
return {
back: [...this.backStack],
current: this.currentPage,
forward: [...this.forwardStack].reverse()
};
}
/**
* 检查是否可以后退
*/
canGoBack(): boolean {
return this.backStack.length > 0;
}
/**
* 检查是否可以前进
*/
canGoForward(): boolean {
return this.forwardStack.length > 0;
}
/**
* 清除所有历史记录
*/
clear(): void {
this.backStack = [];
this.forwardStack = [];
console.log('🗑️ 历史记录已清除');
}
/**
* 打印历史记录(用于调试)
*/
printHistory(): void {
const history = this.getHistory();
console.log('\n📚 历史记录:');
console.log('后退栈:', history.back.join(' → '));
console.log('当前页:', history.current);
console.log('前进栈:', history.forward.join(' → '));
console.log('');
}
}
// 使用示例
const browser = new BrowserHistory("google.com");
browser.visit("youtube.com");
browser.visit("facebook.com");
browser.visit("twitter.com");
browser.printHistory();
// 后退栈: google.com → youtube.com → facebook.com
// 当前页: twitter.com
// 前进栈:
browser.back(2);
browser.printHistory();
// 后退栈: google.com
// 当前页: youtube.com
// 前进栈: facebook.com → twitter.com
browser.forward(1);
browser.printHistory();
// 后退栈: google.com → youtube.com
// 当前页: facebook.com
// 前进栈: twitter.com
browser.visit("instagram.com"); // 访问新页面,清空前进栈
browser.printHistory();
// 后退栈: google.com → youtube.com → facebook.com
// 当前页: instagram.com
// 前进栈:
React 集成示例
/**
* React Hook 封装
*/
import { useState, useCallback } from 'react';
function useBrowserHistory(initialUrl: string) {
const [history] = useState(() => new BrowserHistory(initialUrl));
const [, forceUpdate] = useState({});
const visit = useCallback((url: string) => {
history.visit(url);
forceUpdate({});
}, [history]);
const back = useCallback((steps?: number) => {
history.back(steps);
forceUpdate({});
}, [history]);
const forward = useCallback((steps?: number) => {
history.forward(steps);
forceUpdate({});
}, [history]);
return {
currentPage: history.getCurrentPage(),
canGoBack: history.canGoBack(),
canGoForward: history.canGoForward(),
visit,
back,
forward,
getHistory: () => history.getHistory()
};
}
// 组件中使用
function BrowserDemo() {
const { currentPage, canGoBack, canGoForward, visit, back, forward } =
useBrowserHistory('google.com');
return (
<div>
<div>当前页面: {currentPage}</div>
<button onClick={() => back()} disabled={!canGoBack}>后退</button>
<button onClick={() => forward()} disabled={!canGoForward}>前进</button>
<button onClick={() => visit('youtube.com')}>访问 YouTube</button>
</div>
);
}
二、撤销重做系统
需求分析
实现一个通用的撤销重做系统,支持:
- 记录操作历史
- 撤销操作
- 重做操作
- 批量操作
- 操作合并
- 历史记录限制
完整实现
/**
* 操作接口
*/
interface Action<T = any> {
type: string; // 操作类型
execute: () => void; // 执行操作
undo: () => void; // 撤销操作
data?: T; // 操作数据
timestamp: number; // 时间戳
}
/**
* 撤销重做管理器
*/
class UndoRedoManager<T = any> {
private undoStack: Action<T>[] = [];
private redoStack: Action<T>[] = [];
private maxSize: number;
private isExecuting: boolean = false;
constructor(maxSize: number = 100) {
this.maxSize = maxSize;
}
/**
* 执行新操作
*/
execute(action: Action<T>): void {
if (this.isExecuting) return;
this.isExecuting = true;
try {
// 执行操作
action.execute();
// 添加到撤销栈
this.undoStack.push({
...action,
timestamp: Date.now()
});
// 清空重做栈
this.redoStack = [];
// 限制栈大小
if (this.undoStack.length > this.maxSize) {
this.undoStack.shift();
}
console.log(`✅ 执行: ${action.type}`);
} finally {
this.isExecuting = false;
}
}
/**
* 撤销操作
*/
undo(): boolean {
if (this.undoStack.length === 0) {
console.log('⚠️ 没有可撤销的操作');
return false;
}
this.isExecuting = true;
try {
const action = this.undoStack.pop()!;
action.undo();
this.redoStack.push(action);
console.log(`↩️ 撤销: ${action.type}`);
return true;
} finally {
this.isExecuting = false;
}
}
/**
* 重做操作
*/
redo(): boolean {
if (this.redoStack.length === 0) {
console.log('⚠️ 没有可重做的操作');
return false;
}
this.isExecuting = true;
try {
const action = this.redoStack.pop()!;
action.execute();
this.undoStack.push(action);
console.log(`↪️ 重做: ${action.type}`);
return true;
} finally {
this.isExecuting = false;
}
}
/**
* 批量撤销
*/
undoMultiple(count: number): number {
let undone = 0;
while (count > 0 && this.undo()) {
undone++;
count--;
}
return undone;
}
/**
* 批量重做
*/
redoMultiple(count: number): number {
let redone = 0;
while (count > 0 && this.redo()) {
redone++;
count--;
}
return redone;
}
/**
* 清除历史记录
*/
clear(): void {
this.undoStack = [];
this.redoStack = [];
console.log('🗑️ 历史记录已清除');
}
/**
* 获取历史记录
*/
getHistory(): {
undoCount: number;
redoCount: number;
undoList: string[];
redoList: string[];
} {
return {
undoCount: this.undoStack.length,
redoCount: this.redoStack.length,
undoList: this.undoStack.map(a => a.type),
redoList: this.redoStack.map(a => a.type)
};
}
/**
* 检查是否可以撤销
*/
canUndo(): boolean {
return this.undoStack.length > 0;
}
/**
* 检查是否可以重做
*/
canRedo(): boolean {
return this.redoStack.length > 0;
}
}
// 使用示例:富文本编辑器
interface TextState {
content: string;
cursorPosition: number;
}
class TextEditor {
private content: string = '';
private undoRedo: UndoRedoManager<TextState>;
constructor() {
this.undoRedo = new UndoRedoManager<TextState>();
}
/**
* 插入文本
*/
insertText(text: string, position: number): void {
const oldContent = this.content;
const newContent =
oldContent.slice(0, position) + text + oldContent.slice(position);
this.undoRedo.execute({
type: `插入文本: "${text}"`,
execute: () => {
this.content = newContent;
},
undo: () => {
this.content = oldContent;
},
data: { content: newContent, cursorPosition: position + text.length },
timestamp: Date.now()
});
}
/**
* 删除文本
*/
deleteText(start: number, end: number): void {
const oldContent = this.content;
const deletedText = oldContent.slice(start, end);
const newContent = oldContent.slice(0, start) + oldContent.slice(end);
this.undoRedo.execute({
type: `删除文本: "${deletedText}"`,
execute: () => {
this.content = newContent;
},
undo: () => {
this.content = oldContent;
},
data: { content: newContent, cursorPosition: start },
timestamp: Date.now()
});
}
/**
* 替换文本
*/
replaceText(start: number, end: number, newText: string): void {
const oldContent = this.content;
const replacedText = oldContent.slice(start, end);
const newContent =
oldContent.slice(0, start) + newText + oldContent.slice(end);
this.undoRedo.execute({
type: `替换文本: "${replacedText}" → "${newText}"`,
execute: () => {
this.content = newContent;
},
undo: () => {
this.content = oldContent;
},
data: { content: newContent, cursorPosition: start + newText.length },
timestamp: Date.now()
});
}
/**
* 撤销
*/
undo(): void {
this.undoRedo.undo();
}
/**
* 重做
*/
redo(): void {
this.undoRedo.redo();
}
/**
* 获取内容
*/
getContent(): string {
return this.content;
}
/**
* 打印历史
*/
printHistory(): void {
const history = this.undoRedo.getHistory();
console.log('\n📝 编辑历史:');
console.log('可撤销:', history.undoList.join(' → '));
console.log('可重做:', history.redoList.join(' → '));
console.log('当前内容:', this.content);
console.log('');
}
}
// 测试
const editor = new TextEditor();
editor.insertText('Hello', 0);
editor.insertText(' World', 5);
editor.insertText('!', 11);
editor.printHistory();
// 当前内容: Hello World!
editor.undo();
editor.printHistory();
// 当前内容: Hello World
editor.undo();
editor.printHistory();
// 当前内容: Hello
editor.redo();
editor.printHistory();
// 当前内容: Hello World
editor.replaceText(6, 11, 'TypeScript');
editor.printHistory();
// 当前内容: Hello TypeScript
快捷键绑定
/**
* 绑定键盘快捷键
*/
function setupUndoRedoShortcuts(editor: TextEditor) {
document.addEventListener('keydown', (e) => {
// Ctrl+Z / Cmd+Z: 撤销
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
editor.undo();
}
// Ctrl+Shift+Z / Cmd+Shift+Z: 重做
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && e.shiftKey) {
e.preventDefault();
editor.redo();
}
// Ctrl+Y / Cmd+Y: 重做(Windows 风格)
if ((e.ctrlKey || e.metaKey) && e.key === 'y') {
e.preventDefault();
editor.redo();
}
});
}
三、异步任务队列
需求分析
实现一个异步任务队列,支持:
- 控制并发数
- 任务优先级
- 任务取消
- 错误处理
- 进度追踪
完整实现
/**
* 任务接口
*/
interface Task<T = any> {
id: string;
fn: () => Promise<T>;
priority?: number; // 优先级(数字越大越优先)
timeout?: number; // 超时时间(毫秒)
retries?: number; // 重试次数
}
/**
* 任务结果
*/
interface TaskResult<T = any> {
id: string;
status: 'success' | 'error' | 'timeout' | 'cancelled';
data?: T;
error?: Error;
duration: number;
}
/**
* 异步任务队列
*/
class AsyncTaskQueue<T = any> {
private queue: Task<T>[] = [];
private running: Map<string, Promise<any>> = new Map();
private results: Map<string, TaskResult<T>> = new Map();
private maxConcurrent: number;
private paused: boolean = false;
constructor(maxConcurrent: number = 3) {
this.maxConcurrent = maxConcurrent;
}
/**
* 添加任务
*/
async add(task: Task<T>): Promise<TaskResult<T>> {
return new Promise((resolve, reject) => {
const wrappedTask: Task<T> = {
...task,
priority: task.priority || 0,
fn: async () => {
const startTime = Date.now();
try {
// 设置超时
const timeoutPromise = task.timeout
? new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Task timeout')), task.timeout)
)
: null;
// 执行任务
const result = timeoutPromise
? await Promise.race([task.fn(), timeoutPromise])
: await task.fn();
const taskResult: TaskResult<T> = {
id: task.id,
status: 'success',
data: result,
duration: Date.now() - startTime
};
this.results.set(task.id, taskResult);
resolve(taskResult);
return result;
} catch (error) {
const taskResult: TaskResult<T> = {
id: task.id,
status: error instanceof Error && error.message === 'Task timeout'
? 'timeout'
: 'error',
error: error as Error,
duration: Date.now() - startTime
};
this.results.set(task.id, taskResult);
// 重试逻辑
if (task.retries && task.retries > 0) {
console.log(`🔄 重试任务 ${task.id}, 剩余次数: ${task.retries}`);
return this.add({ ...task, retries: task.retries - 1 });
}
reject(taskResult);
throw error;
}
}
};
// 按优先级插入队列
this.insertByPriority(wrappedTask);
this.run();
});
}
/**
* 按优先级插入任务
*/
private insertByPriority(task: Task<T>): void {
const index = this.queue.findIndex(t => (t.priority || 0) < (task.priority || 0));
if (index === -1) {
this.queue.push(task);
} else {
this.queue.splice(index, 0, task);
}
console.log(`➕ 添加任务 ${task.id} (优先级: ${task.priority || 0})`);
}
/**
* 执行队列中的任务
*/
private async run(): Promise<void> {
if (this.paused) return;
while (
this.running.size < this.maxConcurrent &&
this.queue.length > 0
) {
const task = this.queue.shift()!;
console.log(`▶️ 开始执行任务 ${task.id}`);
const promise = task.fn().finally(() => {
this.running.delete(task.id);
console.log(`✅ 完成任务 ${task.id}`);
this.run(); // 继续执行下一个任务
});
this.running.set(task.id, promise);
}
}
/**
* 取消任务
*/
cancel(taskId: string): boolean {
// 从队列中移除
const index = this.queue.findIndex(t => t.id === taskId);
if (index !== -1) {
this.queue.splice(index, 1);
this.results.set(taskId, {
id: taskId,
status: 'cancelled',
duration: 0
});
console.log(`❌ 取消任务 ${taskId}`);
return true;
}
return false;
}
/**
* 暂停队列
*/
pause(): void {
this.paused = true;
console.log('⏸️ 队列已暂停');
}
/**
* 恢复队列
*/
resume(): void {
this.paused = false;
console.log('▶️ 队列已恢复');
this.run();
}
/**
* 清空队列
*/
clear(): void {
this.queue = [];
console.log('🗑️ 队列已清空');
}
/**
* 等待所有任务完成
*/
async waitAll(): Promise<TaskResult<T>[]> {
while (this.queue.length > 0 || this.running.size > 0) {
await Promise.all(this.running.values());
await new Promise(resolve => setTimeout(resolve, 10));
}
return Array.from(this.results.values());
}
/**
* 获取队列状态
*/
getStatus(): {
pending: number;
running: number;
completed: number;
failed: number;
} {
const results = Array.from(this.results.values());
return {
pending: this.queue.length,
running: this.running.size,
completed: results.filter(r => r.status === 'success').length,
failed: results.filter(r => r.status === 'error' || r.status === 'timeout').length
};
}
/**
* 获取任务结果
*/
getResult(taskId: string): TaskResult<T> | undefined {
return this.results.get(taskId);
}
}
// 使用示例
const taskQueue = new AsyncTaskQueue(2); // 最多同时执行2个任务
// 模拟 API 请求
const fetchData = (id: number, delay: number): Promise<string> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`数据 ${id}`);
}, delay);
});
};
// 添加任务
taskQueue.add({
id: 'task-1',
fn: () => fetchData(1, 2000),
priority: 1
});
taskQueue.add({
id: 'task-2',
fn: () => fetchData(2, 1000),
priority: 2 // 高优先级
});
taskQueue.add({
id: 'task-3',
fn: () => fetchData(3, 1500),
timeout: 1000, // 1秒超时
retries: 2 // 重试2次
});
taskQueue.add({
id: 'task-4',
fn: () => fetchData(4, 500)
});
// 等待所有任务完成
taskQueue.waitAll().then(results => {
console.log('\n📊 任务完成统计:');
console.log('总任务数:', results.length);
console.log('成功:', results.filter(r => r.status === 'success').length);
console.log('失败:', results.filter(r => r.status === 'error').length);
console.log('超时:', results.filter(r => r.status === 'timeout').length);
});
实际应用场景
/**
* 图片批量上传
*/
async function uploadImages(files: File[]) {
const uploadQueue = new AsyncTaskQueue(3); // 最多同时上传3张
const tasks = files.map((file, index) => ({
id: `upload-${index}`,
fn: async () => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return response.json();
},
timeout: 30000, // 30秒超时
retries: 2 // 失败重试2次
}));
// 添加所有任务
const promises = tasks.map(task => uploadQueue.add(task));
// 等待所有上传完成
const results = await Promise.allSettled(promises);
return results;
}
四、LRU 缓存实现
需求分析
实现一个 LRU (Least Recently Used) 缓存,支持:
- O(1) 的读写操作
- 自动淘汰最久未使用的数据
- 容量限制
- 过期时间
完整实现
/**
* 缓存项
*/
interface CacheItem<T> {
key: string;
value: T;
expireTime?: number; // 过期时间戳
}
/**
* LRU 缓存
*/
class LRUCache<T = any> {
private capacity: number;
private cache: Map<string, T>;
private expireMap: Map<string, number>;
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map();
this.expireMap = new Map();
}
/**
* 获取值
*/
get(key: string): T | undefined {
// 检查是否过期
if (this.isExpired(key)) {
this.delete(key);
return undefined;
}
if (!this.cache.has(key)) {
return undefined;
}
// 更新访问顺序(删除后重新插入)
const value = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, value);
console.log(`🔍 读取缓存: ${key}`);
return value;
}
/**
* 设置值
*/
set(key: string, value: T, ttl?: number): void {
// 如果键已存在,先删除
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 插入新值
this.cache.set(key, value);
// 设置过期时间
if (ttl) {
this.expireMap.set(key, Date.now() + ttl);
}
// 如果超出容量,删除最久未使用的(Map 的第一个元素)
if (this.cache.size > this.capacity) {
const firstKey = this.cache.keys().next().value;
this.delete(firstKey);
console.log(`🗑️ 淘汰缓存: ${firstKey}`);
}
console.log(`💾 写入缓存: ${key}`);
}
/**
* 删除键
*/
delete(key: string): boolean {
this.expireMap.delete(key);
return this.cache.delete(key);
}
/**
* 检查是否过期
*/
private isExpired(key: string): boolean {
const expireTime = this.expireMap.get(key);
if (!expireTime) return false;
if (Date.now() > expireTime) {
return true;
}
return false;
}
/**
* 清理过期缓存
*/
cleanup(): number {
let cleaned = 0;
for (const [key, expireTime] of this.expireMap.entries()) {
if (Date.now() > expireTime) {
this.delete(key);
cleaned++;
}
}
if (cleaned > 0) {
console.log(`🧹 清理了 ${cleaned} 个过期缓存`);
}
return cleaned;
}
/**
* 清空缓存
*/
clear(): void {
this.cache.clear();
this.expireMap.clear();
console.log('🗑️ 缓存已清空');
}
/**
* 获取缓存大小
*/
size(): number {
return this.cache.size;
}
/**
* 检查键是否存在
*/
has(key: string): boolean {
if (this.isExpired(key)) {
this.delete(key);
return false;
}
return this.cache.has(key);
}
/**
* 获取所有键
*/
keys(): string[] {
// 清理过期缓存
this.cleanup();
return Array.from(this.cache.keys());
}
/**
* 打印缓存状态
*/
print(): void {
console.log('\n📦 缓存内容:');
console.log('容量:', `${this.cache.size}/${this.capacity}`);
console.log('键值对:', Array.from(this.cache.entries()));
console.log('');
}
}
// 测试
const cache = new LRUCache<string>(3);
cache.set('a', '数据A');
cache.set('b', '数据B');
cache.set('c', '数据C');
cache.print();
// 容量: 3/3
console.log(cache.get('a')); // 访问 a,更新顺序
cache.set('d', '数据D'); // 淘汰 b
cache.print();
// 容量: 3/3, 键: a, c, d
// 测试过期时间
cache.set('e', '数据E', 1000); // 1秒后过期
setTimeout(() => {
console.log(cache.get('e')); // undefined (已过期)
}, 1500);
实际应用:API 响应缓存
/**
* API 缓存装饰器
*/
class APICache {
private cache: LRUCache<any>;
constructor(capacity: number = 100) {
this.cache = new LRUCache(capacity);
// 每分钟清理一次过期缓存
setInterval(() => this.cache.cleanup(), 60000);
}
/**
* 包装 fetch 请求
*/
async fetch(url: string, options?: RequestInit, ttl: number = 300000): Promise<any> {
const cacheKey = this.getCacheKey(url, options);
// 尝试从缓存读取
const cached = this.cache.get(cacheKey);
if (cached) {
console.log('✅ 命中缓存:', url);
return cached;
}
// 发起请求
console.log('🌐 发起请求:', url);
const response = await fetch(url, options);
const data = await response.json();
// 写入缓存
this.cache.set(cacheKey, data, ttl);
return data;
}
/**
* 生成缓存键
*/
private getCacheKey(url: string, options?: RequestInit): string {
return `${url}:${JSON.stringify(options || {})}`;
}
/**
* 清除指定 URL 的缓存
*/
invalidate(url: string): void {
const keys = this.cache.keys().filter(key => key.startsWith(url));
keys.forEach(key => this.cache.delete(key));
console.log(`🗑️ 清除了 ${keys.length} 个相关缓存`);
}
}
// 使用示例
const apiCache = new APICache(50);
// 第一次请求(发起网络请求)
await apiCache.fetch('/api/users', {}, 60000); // 缓存1分钟
// 第二次请求(从缓存读取)
await apiCache.fetch('/api/users', {}, 60000);
// 清除缓存
apiCache.invalidate('/api/users');
五、函数调用栈追踪器
需求分析
实现一个函数调用栈追踪器,用于:
- 调试递归函数
- 性能分析
- 调用链可视化
完整实现
/**
* 调用信息
*/
interface CallInfo {
functionName: string;
args: any[];
startTime: number;
endTime?: number;
result?: any;
error?: Error;
}
/**
* 函数调用栈追踪器
*/
class CallStackTracker {
private stack: CallInfo[] = [];
private callHistory: CallInfo[] = [];
private maxDepth: number = 0;
private enabled: boolean = true;
/**
* 进入函数
*/
enter(functionName: string, args: any[] = []): void {
if (!this.enabled) return;
const callInfo: CallInfo = {
functionName,
args,
startTime: performance.now()
};
this.stack.push(callInfo);
this.maxDepth = Math.max(this.maxDepth, this.stack.length);
this.printIndented(`→ ${functionName}(${this.formatArgs(args)})`);
}
/**
* 退出函数
*/
exit(result?: any): void {
if (!this.enabled) return;
const callInfo = this.stack.pop();
if (!callInfo) return;
callInfo.endTime = performance.now();
callInfo.result = result;
const duration = callInfo.endTime - callInfo.startTime;
this.callHistory.push(callInfo);
this.printIndented(
`← ${callInfo.functionName} (${duration.toFixed(2)}ms)`,
this.stack.length
);
}
/**
* 记录错误
*/
error(error: Error): void {
if (!this.enabled) return;
const callInfo = this.stack[this.stack.length - 1];
if (callInfo) {
callInfo.error = error;
callInfo.endTime = performance.now();
}
this.printIndented(`❌ ${error.message}`);
}
/**
* 打印缩进的日志
*/
private printIndented(message: string, depth?: number): void {
const indent = ' '.repeat(depth ?? this.stack.length);
console.log(`${indent}${message}`);
}
/**
* 格式化参数
*/
private formatArgs(args: any[]): string {
return args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return String(arg);
}).join(', ');
}
/**
* 获取统计信息
*/
getStats(): {
maxDepth: number;
totalCalls: number;
averageDuration: number;
slowestCalls: CallInfo[];
} {
const durations = this.callHistory
.filter(c => c.endTime)
.map(c => c.endTime! - c.startTime);
const avgDuration = durations.length > 0
? durations.reduce((a, b) => a + b, 0) / durations.length
: 0;
const slowest = [...this.callHistory]
.filter(c => c.endTime)
.sort((a, b) => (b.endTime! - b.startTime) - (a.endTime! - a.startTime))
.slice(0, 5);
return {
maxDepth: this.maxDepth,
totalCalls: this.callHistory.length,
averageDuration: avgDuration,
slowestCalls: slowest
};
}
/**
* 启用/禁用追踪
*/
setEnabled(enabled: boolean): void {
this.enabled = enabled;
}
/**
* 清除历史
*/
clear(): void {
this.stack = [];
this.callHistory = [];
this.maxDepth = 0;
}
}
// 使用示例:追踪斐波那契函数
const tracker = new CallStackTracker();
function fibonacci(n: number): number {
tracker.enter('fibonacci', [n]);
if (n <= 1) {
tracker.exit(n);
return n;
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
tracker.exit(result);
return result;
}
console.log('\n🔍 追踪 fibonacci(4):\n');
const result = fibonacci(4);
console.log(`\n结果: ${result}\n`);
const stats = tracker.getStats();
console.log('📊 统计信息:');
console.log('最大深度:', stats.maxDepth);
console.log('总调用次数:', stats.totalCalls);
console.log('平均耗时:', stats.averageDuration.toFixed(2), 'ms');
// 输出:
// → fibonacci(4)
// → fibonacci(3)
// → fibonacci(2)
// → fibonacci(1)
// ← fibonacci (0.05ms)
// → fibonacci(0)
// ← fibonacci (0.03ms)
// ← fibonacci (0.15ms)
// → fibonacci(1)
// ← fibonacci (0.03ms)
// ← fibonacci (0.25ms)
// → fibonacci(2)
// → fibonacci(1)
// ← fibonacci (0.03ms)
// → fibonacci(0)
// ← fibonacci (0.02ms)
// ← fibonacci (0.12ms)
// ← fibonacci (0.42ms)
装饰器版本
/**
* 函数追踪装饰器
*/
function trace(tracker: CallStackTracker) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
tracker.enter(propertyKey, args);
try {
const result = originalMethod.apply(this, args);
// 处理 Promise
if (result instanceof Promise) {
return result
.then(value => {
tracker.exit(value);
return value;
})
.catch(error => {
tracker.error(error);
throw error;
});
}
tracker.exit(result);
return result;
} catch (error) {
tracker.error(error as Error);
throw error;
}
};
return descriptor;
};
}
// 使用装饰器
class Calculator {
@trace(tracker)
factorial(n: number): number {
if (n <= 1) return 1;
return n * this.factorial(n - 1);
}
}
六、表达式求值器
需求分析
实现一个完整的表达式求值器,支持:
- 四则运算
- 括号
- 变量
- 函数调用
完整实现
/**
* 表达式求值器
*/
class ExpressionEvaluator {
private variables: Map<string, number> = new Map();
private functions: Map<string, (...args: number[]) => number> = new Map();
constructor() {
// 注册内置函数
this.registerFunction('abs', Math.abs);
this.registerFunction('sqrt', Math.sqrt);
this.registerFunction('pow', Math.pow);
this.registerFunction('max', Math.max);
this.registerFunction('min', Math.min);
}
/**
* 注册变量
*/
setVariable(name: string, value: number): void {
this.variables.set(name, value);
}
/**
* 注册函数
*/
registerFunction(name: string, fn: (...args: number[]) => number): void {
this.functions.set(name, fn);
}
/**
* 求值
*/
evaluate(expression: string): number {
// 1. 词法分析:分词
const tokens = this.tokenize(expression);
// 2. 语法分析:中缀转后缀
const postfix = this.infixToPostfix(tokens);
// 3. 计算后缀表达式
return this.evaluatePostfix(postfix);
}
/**
* 词法分析
*/
private tokenize(expression: string): string[] {
const tokens: string[] = [];
let current = '';
for (let i = 0; i < expression.length; i++) {
const char = expression[i];
if (char === ' ') {
if (current) {
tokens.push(current);
current = '';
}
} else if ('+-*/()'.includes(char)) {
if (current) {
tokens.push(current);
current = '';
}
tokens.push(char);
} else {
current += char;
}
}
if (current) {
tokens.push(current);
}
return tokens;
}
/**
* 中缀转后缀
*/
private infixToPostfix(tokens: string[]): string[] {
const output: string[] = [];
const operators: string[] = [];
const precedence: Record<string, number> = {
'+': 1, '-': 1,
'*': 2, '/': 2
};
for (const token of tokens) {
if (this.isNumber(token)) {
output.push(token);
} else if (this.isVariable(token)) {
output.push(token);
} else if (this.isFunction(token)) {
operators.push(token);
} else if (token === '(') {
operators.push(token);
} else if (token === ')') {
while (operators.length && operators[operators.length - 1] !== '(') {
output.push(operators.pop()!);
}
operators.pop(); // 弹出 '('
// 如果栈顶是函数,也弹出
if (operators.length && this.isFunction(operators[operators.length - 1])) {
output.push(operators.pop()!);
}
} else if (token in precedence) {
while (
operators.length &&
operators[operators.length - 1] !== '(' &&
precedence[operators[operators.length - 1]] >= precedence[token]
) {
output.push(operators.pop()!);
}
operators.push(token);
}
}
while (operators.length) {
output.push(operators.pop()!);
}
return output;
}
/**
* 计算后缀表达式
*/
private evaluatePostfix(tokens: string[]): number {
const stack: number[] = [];
for (const token of tokens) {
if (this.isNumber(token)) {
stack.push(parseFloat(token));
} else if (this.isVariable(token)) {
const value = this.variables.get(token);
if (value === undefined) {
throw new Error(`未定义的变量: ${token}`);
}
stack.push(value);
} else if (this.isFunction(token)) {
const fn = this.functions.get(token);
if (!fn) {
throw new Error(`未定义的函数: ${token}`);
}
// 函数参数个数(简化处理,假设都是2个参数)
const b = stack.pop()!;
const a = stack.pop()!;
stack.push(fn(a, b));
} else {
const b = stack.pop()!;
const a = stack.pop()!;
switch (token) {
case '+': stack.push(a + b); break;
case '-': stack.push(a - b); break;
case '*': stack.push(a * b); break;
case '/': stack.push(a / b); break;
}
}
}
return stack[0];
}
/**
* 判断是否为数字
*/
private isNumber(token: string): boolean {
return !isNaN(parseFloat(token));
}
/**
* 判断是否为变量
*/
private isVariable(token: string): boolean {
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(token) && !this.functions.has(token);
}
/**
* 判断是否为函数
*/
private isFunction(token: string): boolean {
return this.functions.has(token);
}
}
// 测试
const evaluator = new ExpressionEvaluator();
console.log(evaluator.evaluate('3 + 5 * 2')); // 13
console.log(evaluator.evaluate('(1 + 2) * 3')); // 9
console.log(evaluator.evaluate('10 + 2 * 6')); // 22
// 使用变量
evaluator.setVariable('x', 10);
evaluator.setVariable('y', 5);
console.log(evaluator.evaluate('x + y * 2')); // 20
// 使用函数
console.log(evaluator.evaluate('max(3, 5)')); // 5
console.log(evaluator.evaluate('pow(2, 3)')); // 8
console.log(evaluator.evaluate('sqrt(16) + abs(-5)')); // 9
七、性能对比与总结
性能测试
/**
* 性能测试工具
*/
function benchmark(name: string, fn: () => void, iterations: number = 10000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
const duration = end - start;
const avgTime = duration / iterations;
console.log(`\n📊 ${name}`);
console.log(`总耗时: ${duration.toFixed(2)}ms`);
console.log(`平均耗时: ${avgTime.toFixed(4)}ms`);
console.log(`吞吐量: ${(iterations / duration * 1000).toFixed(0)} ops/s`);
}
// 测试数组队列 vs 循环队列
console.log('=== 队列性能对比 ===');
benchmark('数组队列', () => {
const queue: number[] = [];
for (let i = 0; i < 100; i++) {
queue.push(i);
queue.shift();
}
});
benchmark('循环队列', () => {
const queue = new CircularQueue<number>(100);
for (let i = 0; i < 100; i++) {
queue.enqueue(i);
queue.dequeue();
}
});
// 输出示例:
// 📊 数组队列
// 总耗时: 1250.50ms
// 平均耗时: 0.1251ms
// 吞吐量: 7997 ops/s
//
// 📊 循环队列
// 总耗时: 85.20ms
// 平均耗时: 0.0085ms
// 吞吐量: 117371 ops/s
最佳实践总结
| 场景 | 推荐方案 | 原因 | |------|---------|------| | 浏览器历史 | 双栈 | 支持前进后退 | | 撤销重做 | 双栈 + 时间戳 | 需要记录操作历史 | | 异步任务 | 优先队列 + 并发控制 | 支持优先级和限流 | | API 缓存 | LRU + 过期时间 | 自动淘汰 + 时效性 | | 表达式求值 | 双栈(运算符+操作数) | 经典算法 | | 函数追踪 | 栈 + 性能计时 | 记录调用链 | | 高频出队 | 循环队列 | O(1) 性能 |
常见陷阱
- 内存泄漏
// ❌ 队列不断增长
const queue: any[] = [];
setInterval(() => {
queue.push(data);
// 忘记出队
}, 1000);
// ✅ 限制队列大小
if (queue.length > MAX_SIZE) {
queue.shift();
}
- 栈溢出
// ❌ 递归深度过大
function deepRecursion(n: number): number {
if (n === 0) return 0;
return deepRecursion(n - 1); // 可能栈溢出
}
// ✅ 改用迭代
function iterative(n: number): number {
let result = 0;
while (n > 0) {
result += n--;
}
return result;
}
- 并发问题
// ❌ 没有并发控制
const promises = urls.map(url => fetch(url)); // 同时发起所有请求
// ✅ 使用任务队列
const queue = new AsyncTaskQueue(5);
const promises = urls.map(url =>
queue.add({ id: url, fn: () => fetch(url) })
);
八、总结
核心要点
- 浏览器历史: 双栈实现前进后退
- 撤销重做: 栈 + 命令模式
- 异步任务: 队列 + 并发控制 + 优先级
- LRU 缓存: Map + 访问顺序 + 过期时间
- 表达式求值: 双栈 + 调度场算法
- 函数追踪: 栈 + 性能计时
性能优化建议
- ✅ 使用循环队列代替数组队列
- ✅ 为 LRU 缓存设置合理容量
- ✅ 异步任务队列要限制并发数
- ✅ 定期清理过期数据
- ✅ 使用对象池减少 GC 压力
实际应用场景
- 🌐 浏览器:历史记录、标签页管理
- ✏️ 编辑器:撤销重做、语法高亮
- 🎨 设计工具:图层管理、操作历史
- 📡 网络:请求队列、缓存管理
- 🎮 游戏:状态回溯、动画队列
系列回顾
本系列我们学习了栈与队列的:
基础篇:
- 栈和队列的基本概念
- 基础实现和经典问题
- 括号匹配、用栈实现队列
进阶篇:
- 循环队列 - O(1) 出队
- 单调栈 - 下一个更大元素
- 最小栈 - O(1) 获取最小值
- 单调队列 - 滑动窗口最大值
实战篇:
- 浏览器历史记录系统
- 撤销重做系统
- 异步任务队列
- LRU 缓存
- 函数调用栈追踪
- 表达式求值器
下期预告
下一篇我们将学习链表操作,探讨:
- 链表的基本操作
- 双向链表与循环链表
- LRU 缓存的链表实现
- React Fiber 的链表结构
敬请期待!
相关资源: