调试器是代码编辑器的核心功能之一,它让开发者能够在代码执行过程中检查变量值、控制程序流程、定位问题根源。本文详细介绍断点管理、变量检查、调用栈分析、断点条件与调试控制的设计与实现。
调试器架构
整体架构设计
/* 调试器架构 */
class Debugger {
constructor(editor) {
this.editor = editor;
this.breakpoints = new Map(); // 断点集合
this.debugSession = null; // 当前调试会话
this.callStack = []; // 调用栈
this.variables = new Map(); // 当前变量
this.status = 'idle';
}
}
/* 调试类型定义 */
interface Breakpoint {
id: string;
source: string; // 文件路径
line: number; // 行号
column?: number; // 列号
condition?: string; // 条件断点
logMessage?: string; // 日志消息
enabled: boolean;
hitCondition?: string; // 命中条件
}
interface DebugSession {
id: string;
type: 'node' | 'browser' | 'native';
status: 'running' | 'paused' | 'stopped';
currentFrame: StackFrame;
}
断点管理
断点数据模型
// 断点管理器
class BreakpointManager {
constructor() {
this.breakpoints = new Map(); // source -> Breakpoint[]
this.breakpointIdCounter = 0;
}
addBreakpoint(source, line, options = {}) {
const breakpoint = {
id: ++this.breakpointIdCounter,
source,
line,
column: options.column || 0,
condition: options.condition,
logMessage: options.logMessage,
enabled: options.enabled !== false,
hitCondition: options.hitCondition,
hitCount: 0
};
if (!this.breakpoints.has(source)) {
this.breakpoints.set(source, []);
}
this.breakpoints.get(source).push(breakpoint);
this.updateEditorDecorations();
return breakpoint;
}
removeBreakpoint(id) {
for (const [source, bps] of this.breakpoints) {
const index = bps.findIndex(bp => bp.id === id);
if (index !== -1) {
bps.splice(index, 1);
this.updateEditorDecorations();
return true;
}
}
return false;
}
getBreakpointsForSource(source) {
return this.breakpoints.get(source) || [];
}
toggleBreakpoint(source, line) {
const bps = this.getBreakpointsForSource(source);
const existing = bps.find(bp => bp.line === line);
if (existing) {
this.removeBreakpoint(existing.id);
return null;
} else {
return this.addBreakpoint(source, line);
}
}
}
断点装饰器
// 断点装饰器 - 在编辑器中显示断点
class BreakpointDecoration {
constructor(editor) {
this.editor = editor;
this.decorations = new Map();
}
updateEditorDecorations() {
const decorations = [];
for (const [source, bps] of breakpointManager.breakpoints) {
for (const bp of bps) {
if (!bp.enabled) continue;
const decoration = {
range: new Range(bp.line, 0, bp.line, 1),
options: {
isWholeLine: true,
className: 'debug-breakpoint',
glyphMarginClassName: 'debug-breakpoint-glyph',
glyphMarginHoverMessage: `断点: 第 ${bp.line} 行`
}
};
if (bp.condition) {
decoration.options.glyphMarginHoverMessage += `\n条件: ${bp.condition}`;
}
decorations.push(decoration);
}
}
this.editor.deltaDecorations(this.decorations, decorations);
}
}
/* 断点装饰器CSS样式 */
.debug-breakpoint {
background: rgba(239, 68, 68, .15);
}
.debug-breakpoint-glyph::before {
content: '';
display: block;
width: 10px;
height: 10px;
background: var(--accent);
border-radius: 50%;
margin: 5px;
}
.debug-breakpoint-log::before {
background: #fbbf24; // 黄色日志断点
}
.debug-breakpoint-conditional::before {
background: #a78bfa; // 紫色条件断点
}
条件断点与日志断点
条件断点实现
// 条件断点评估
class ConditionalBreakpoint {
evaluate(condition, scope) {
try {
// 创建安全的求值函数
const keys = Object.keys(scope);
const values = Object.values(scope);
const fn = new Function(...keys, `return ${condition}`);
return fn(...values);
} catch (e) {
console.error('条件断点求值失败:', e);
return false;
}
}
checkHitCondition(breakpoint) {
if (!breakpoint.hitCondition) return true;
const bp = breakpoint;
bp.hitCount++;
// 解析命中条件: >n, ==n, %n
const match = bp.hitCondition.match(/^(>|==|%)(\d+)$/);
if (!match) return true;
const [, op, count] = match;
const n = parseInt(count);
switch (op) {
case '>': return bp.hitCount > n;
case '==': return bp.hitCount === n;
case '%': return bp.hitCount % n === 0;
}
return true;
}
}
日志断点
// 日志断点 - 不暂停程序,只输出日志
class Logpoint {
constructor(message, scope) {
this.message = message;
this.scope = scope;
}
evaluate() {
// 替换消息中的占位符 {变量名}
let result = this.message;
for (const [key, value] of Object.entries(this.scope)) {
const placeholder = `{${key}}`;
if (result.includes(placeholder)) {
result = result.replace(placeholder, this.formatValue(value));
}
}
return result;
}
formatValue(value) {
if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
}
return String(value);
}
log(outputPanel) {
const message = this.evaluate();
outputPanel.append(`[Logpoint] ${message}`);
}
}
变量检查与监视
变量作用域
// 变量作用域管理
class ScopeManager {
constructor() {
this.scopes = [];
}
buildScopeChain(debugStop) {
const scopes = [];
// 全局作用域
scopes.push({
name: 'Global',
type: 'global',
variables: this.getGlobalVariables()
});
// 闭包作用域
for (const closure of debugStop.closures) {
scopes.push({
name: closure.name || 'Closure',
type: 'closure',
variables: closure.variables
});
}
// 局部作用域
scopes.push({
name: 'Local',
type: 'local',
variables: debugStop.localVariables
});
// 块级作用域
for (const block of debugStop.blockScopes) {
scopes.push({
name: block.name,
type: block.type,
variables: block.variables
});
}
this.scopes = scopes;
return scopes;
}
resolveVariable(name) {
for (const scope of this.scopes) {
if (scope.variables.has(name)) {
return { scope: scope.name, value: scope.variables.get(name) };
}
}
return null;
}
}
变量视图渲染
// 变量视图渲染
class VariablesView {
constructor(container) {
this.container = container;
}
render(scopes) {
let html = '';
for (const scope of scopes) {
html += ` class="scope-group">`;
html += ` class="scope-header" data-expanded="true">`;
html += ` class="icon">▼`;
html += `${scope.name}`;
html += `${scope.type}`;
html += ``;
html += ` class="scope-variables">`;
for (const [name, value] of Object.entries(scope.variables)) {
html += this.renderVariable(name, value);
}
html += ``;
html += ``;
}
this.container.innerHTML = html;
this.attachEvents();
}
renderVariable(name, value) {
const type = this.getType(value);
const displayValue = this.formatValue(value);
const expandable = this.isExpandable(value);
return `
class="variable ${expandable ? 'expandable' : ''}" data-name="${name}">
class="var-name">${name}
class="var-type">${type}
class="var-value">${displayValue}
`;
}
isExpandable(value) {
return typeof value === 'object' && value !== null;
}
}
调用栈
调用栈管理
// 调用栈管理
class CallStackManager {
constructor() {
this.frames = [];
this.selectedFrameId = 0;
}
updateFrames(stackFrames) {
this.frames = stackFrames.map((frame, index) => ({
id: index,
name: frame.functionName,
source: frame.source,
line: frame.line,
column: frame.column,
scope: frame.scope
}));
}
getFrameAt(index) {
return this.frames[index];
}
selectFrame(index) {
if (index >= 0 && index < this.frames.length) {
this.selectedFrameId = index;
this.emitFrameChanged();
}
}
getCurrentFrame() {
return this.frames[this.selectedFrameId];
}
}
调用栈视图
// 调用栈视图渲染
class CallStackView {
render(frames, selectedId) {
const html = frames.map((frame, index) => {
const isSelected = index === selectedId;
const location = `${frame.source}:${frame.line}:${frame.column}`;
return `
class="stack-frame ${isSelected ? 'selected' : ''}" data-frame-id="${index}">
class="frame-index">${index}
class="frame-name">${frame.name || '(anonymous)'}
class="frame-location">${location}
`;
}).join('');
this.container.innerHTML = html;
}
}
调试控制
调试操作
// 调试控制器
class DebugController {
constructor(debugger) {
this.debugger = debugger;
}
start() {
this.debugger.status = 'running';
this.debugger.sendCommand('launch');
}
pause() {
this.debugger.sendCommand('pause');
}
continue() {
this.debugger.status = 'running';
this.debugger.sendCommand('continue');
}
stepOver() {
this.debugger.status = 'paused';
this.debugger.sendCommand('next');
}
stepInto() {
this.debugger.status = 'paused';
this.debugger.sendCommand('stepIn');
}
stepOut() {
this.debugger.status = 'paused';
this.debugger.sendCommand('stepOut');
}
stop() {
this.debugger.status = 'stopped';
this.debugger.sendCommand('disconnect');
}
}
调试UI面板
// 调试工具栏
class DebugToolbar {
render(status) {
const canContinue = status === 'paused';
const canPause = status === 'running';
return `
class="debug-toolbar">
`;
}
}
调试控制台集成
REPL控制台
// 调试REPL控制台
class DebugConsole {
constructor(scopeManager) {
this.scopeManager = scopeManager;
}
execute(input) {
try {
// 获取当前作用域的所有变量
const scopeVars = {};
for (const scope of this.scopeManager.scopes) {
Object.assign(scopeVars, scope.variables);
}
// 执行表达式
const keys = Object.keys(scopeVars);
const values = Object.values(scopeVars);
const fn = new Function(...keys, `return ${input}`);
const result = fn(...values);
return { success: true, value: this.formatResult(result) };
} catch (e) {
return { success: false, error: e.message };
}
}
formatResult(value) {
if (typeof value === 'object' && value !== null) {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
return String(value);
}
}
总结
- 断点管理:支持行断点、条件断点、日志断点,可设置命中次数
- 断点装饰器:在编辑器中可视化显示断点位置
- 变量检查:展示作用域链中的变量,支持展开查看对象属性
- 调用栈:显示函数调用链,支持选择不同帧查看局部变量
- 调试控制:支持启动、暂停、继续、单步执行、停止等操作
- 调试REPL:在调试过程中执行表达式,实时查看结果
一个功能完善的调试器能够显著提升开发效率,帮助开发者快速定位和解决问题。