Appearance
JavaScript 调试技巧
JavaScript调试是开发过程中的重要技能。掌握有效的调试技巧可以显著提高开发效率,快速定位和解决问题。
调试工具概览
浏览器开发者工具
现代浏览器都提供了强大的开发者工具,是JavaScript调试的主要工具。
主要面板功能:
- Elements: 查看和编辑DOM结构
- Console: 查看日志、执行代码、查看错误
- Sources: 设置断点、单步调试、查看调用栈
- Network: 监控网络请求
- Performance: 性能分析
- Memory: 内存分析
- Application: 存储、缓存等应用数据
Node.js 调试工具
bash
node --inspect app.js # 启动调试模式
node --inspect-brk app.js # 在第一行暂停
node --inspect=0.0.0.0:9229 # 允许远程调试
Console API 详解
基本输出方法
javascript
// 1. 基本日志输出
console.log('普通日志');
console.info('信息日志');
console.warn('警告信息');
console.error('错误信息');
// 2. 格式化输出
const user = { name: 'John', age: 30 };
console.log('用户信息: %o', user);
console.log('用户姓名: %s, 年龄: %d', user.name, user.age);
// 3. 对象和数组的特殊输出
console.log('用户对象:', user);
console.table([{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }]); // 以表格形式显示
console.dir(document.body); // 显示对象的属性
// 4. 条件日志
const DEBUG = true;
if (DEBUG) {
console.log('调试信息:', { step: 1, data: 'initial' });
}
// 5. 分组日志
console.group('用户操作流程');
console.log('步骤 1: 用户登录');
console.log('步骤 2: 数据加载');
console.log('步骤 3: 界面更新');
console.groupEnd();
// 带标签的分组
console.group('API调用');
console.log('请求: GET /api/users');
console.log('响应: 200 OK');
console.groupEnd();
高级 Console 技巧
javascript
// 1. 计数器
function processItem(item) {
console.count('处理项目'); // 自动计数
return item * 2;
}
processItem(1); // "处理项目: 1"
processItem(2); // "处理项目: 2"
// 2. 时间测量
console.time('数据处理');
// 执行一些操作
for (let i = 0; i < 1000000; i++) {
Math.random();
}
console.timeEnd('数据处理'); // 显示耗时
// 3. 条件断言
const value = 5;
console.assert(value > 10, '值应该大于10,但实际是:', value);
// 4. 追踪调用栈
function a() {
b();
}
function b() {
c();
}
function c() {
console.trace('调用追踪'); // 显示完整的调用栈
}
a();
// 5. 清空控制台
console.clear(); // 清空控制台
断点调试技术
debugger 语句
javascript
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
const item = items[i];
debugger; // 程序会在这里暂停
if (item.price > 0) {
total += item.price;
}
}
return total;
}
// 使用示例
const items = [
{ name: 'Item 1', price: 10 },
{ name: 'Item 2', price: 20 },
{ name: 'Item 3', price: 30 }
];
const total = calculateTotal(items);
条件断点
javascript
// 在循环中设置条件断点
for (let i = 0; i < 1000; i++) {
const value = processData(i);
// 只在特定条件下暂停
if (i === 500) {
debugger; // 只在i=500时暂停
}
// 或者使用条件表达式
if (value < 0) {
debugger; // 当value为负数时暂停
}
}
// 复杂条件断点
function processArray(arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 在开发者工具中设置断点,条件为: item.id === 123
// 这样可以避免在代码中硬编码debugger
const result = transformItem(item);
}
}
异常断点
javascript
// 使用开发者工具的"Pause on exceptions"功能
function riskyOperation() {
try {
const data = fetchData();
const processed = JSON.parse(data); // 可能抛出异常
return processed;
} catch (error) {
console.error('处理失败:', error);
throw error; // 在这里设置断点可以捕获异常
}
}
// 或者在可能出错的地方设置断点
function parseUserData(userData) {
// 在开发者工具中设置"Pause on exceptions"
// 这样任何异常都会暂停执行
return {
name: userData.name,
email: userData.profile.email, // 如果profile不存在会出错
age: parseInt(userData.age)
};
}
源码映射和压缩代码调试
处理压缩代码
javascript
// 开发时使用source map
// webpack.config.js 示例
module.exports = {
devtool: 'source-map', // 生产环境使用
// devtool: 'eval-source-map', // 开发环境使用
};
// 在压缩代码中设置断点的技巧
function minifiedCode() {
// 即使代码被压缩,source map也能映射到原始代码
var a = 1;
var b = 2;
var c = a + b; // 在这里设置断点
return c;
}
网络请求调试
Fetch API 调试
javascript
// 1. 详细的请求日志
async function debugFetch(url, options = {}) {
console.log('发起请求:', {
url,
method: options.method || 'GET',
headers: options.headers,
body: options.body
});
try {
const response = await fetch(url, options);
const data = await response.json();
console.log('响应结果:', {
status: response.status,
statusText: response.statusText,
headers: [...response.headers.entries()],
data
});
return data;
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
// 2. 请求拦截和日志
class ApiDebugger {
constructor() {
this.originalFetch = window.fetch;
this.requestLog = [];
}
enable() {
const self = this;
window.fetch = function(...args) {
const [url, options = {}] = args;
const logEntry = {
timestamp: Date.now(),
url,
method: options.method || 'GET',
startTime: performance.now(),
requestHeaders: options.headers || {}
};
self.requestLog.push(logEntry);
return self.originalFetch.apply(this, args)
.then(response => {
logEntry.endTime = performance.now();
logEntry.duration = logEntry.endTime - logEntry.startTime;
logEntry.status = response.status;
console.log(`API请求: ${url}`, {
status: response.status,
duration: `${logEntry.duration.toFixed(2)}ms`,
size: response.headers.get('content-length')
});
return response;
})
.catch(error => {
logEntry.error = error.message;
console.error(`API请求失败: ${url}`, error);
throw error;
});
};
}
getLog() {
return this.requestLog;
}
clearLog() {
this.requestLog = [];
}
}
// 使用API调试器
const apiDebugger = new ApiDebugger();
apiDebugger.enable();
内存泄漏调试
检测内存泄漏
javascript
// 1. 监控内存使用
function monitorMemory() {
if (performance.memory) {
const memory = performance.memory;
console.log('内存使用情况:', {
used: Math.round(memory.usedJSHeapSize / 1048576) + ' MB',
total: Math.round(memory.totalJSHeapSize / 1048576) + ' MB',
limit: Math.round(memory.jsHeapSizeLimit / 1048576) + ' MB'
});
}
}
// 定期监控
setInterval(monitorMemory, 5000);
// 2. 事件监听器泄漏检测
class EventLeakDetector {
constructor() {
this.eventCounts = new Map();
}
addEventListener(element, event, handler) {
const key = `${element.tagName}-${event}`;
const count = this.eventCounts.get(key) || 0;
this.eventCounts.set(key, count + 1);
console.debug(`添加事件监听器: ${key}, 当前数量: ${count + 1}`);
element.addEventListener(event, handler);
}
removeEventListener(element, event, handler) {
const key = `${element.tagName}-${event}`;
const count = this.eventCounts.get(key) || 0;
this.eventCounts.set(key, Math.max(0, count - 1));
console.debug(`移除事件监听器: ${key}, 剩余数量: ${Math.max(0, count - 1)}`);
element.removeEventListener(event, handler);
}
getStats() {
return Object.fromEntries(this.eventCounts);
}
}
// 3. 定时器泄漏检测
class TimerLeakDetector {
constructor() {
this.timers = new Map();
this.timerId = 1;
}
setTimeout(callback, delay, ...args) {
const id = this.timerId++;
const timerInfo = {
id,
delay,
created: Date.now(),
callback: callback.toString().substring(0, 50) + '...'
};
this.timers.set(id, timerInfo);
const timeoutId = window.setTimeout(() => {
this.timers.delete(id);
callback.apply(this, args);
}, delay);
return timeoutId;
}
clearTimeout(timeoutId) {
// 找到对应的timer id并删除
for (const [id, info] of this.timers) {
if (info.nativeId === timeoutId) {
this.timers.delete(id);
break;
}
}
return window.clearTimeout(timeoutId);
}
getActiveTimers() {
return Array.from(this.timers.values());
}
}
异步代码调试
Promise 调试
javascript
// 1. Promise 链调试
function debugPromiseChain() {
return Promise.resolve('start')
.then(value => {
console.log('Step 1:', value);
return value + ' -> step1';
})
.then(value => {
console.log('Step 2:', value);
return value + ' -> step2';
})
.then(value => {
console.log('Step 3:', value);
return value + ' -> step3';
})
.catch(error => {
console.error('Promise链错误:', error);
throw error;
});
}
// 2. Promise.all 调试
async function debugPromiseAll() {
const promises = [
fetch('/api/users').then(r => r.json()).then(data => {
console.log('Users loaded:', data.length);
return data;
}),
fetch('/api/posts').then(r => r.json()).then(data => {
console.log('Posts loaded:', data.length);
return data;
}),
fetch('/api/comments').then(r => r.json()).then(data => {
console.log('Comments loaded:', data.length);
return data;
})
];
try {
const results = await Promise.all(promises);
console.log('所有数据加载完成');
return results;
} catch (error) {
console.error('Promise.all失败:', error);
throw error;
}
}
// 3. 未处理的Promise拒绝
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise拒绝:', {
reason: event.reason,
promise: event.promise,
timestamp: event.timeStamp
});
// 阻止默认的错误处理
event.preventDefault();
});
// 4. Promise 状态监控
class PromiseMonitor {
constructor() {
this.pendingPromises = new Map();
this.promiseId = 0;
}
wrap(promise, label = 'anonymous') {
const id = ++this.promiseId;
this.pendingPromises.set(id, { label, startTime: Date.now() });
const monitoredPromise = promise
.then(result => {
const duration = Date.now() - this.pendingPromises.get(id).startTime;
console.log(`Promise完成 [${label}]: ${duration}ms`);
this.pendingPromises.delete(id);
return result;
})
.catch(error => {
const duration = Date.now() - this.pendingPromises.get(id).startTime;
console.error(`Promise失败 [${label}]: ${duration}ms`, error);
this.pendingPromises.delete(id);
throw error;
});
return monitoredPromise;
}
getStats() {
const now = Date.now();
const stats = Array.from(this.pendingPromises.entries()).map(([id, info]) => ({
id,
label: info.label,
duration: now - info.startTime
}));
return {
totalPending: stats.length,
details: stats
};
}
}
// 使用Promise监控器
const promiseMonitor = new PromiseMonitor();
const monitoredPromise = promiseMonitor.wrap(
fetch('/api/data').then(r => r.json()),
'API数据加载'
);
调用栈分析
错误堆栈解析
javascript
// 1. 自定义错误堆栈
class CustomError extends Error {
constructor(message, context = {}) {
super(message);
this.name = this.constructor.name;
this.context = context;
// 保持堆栈跟踪
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toString() {
return `${this.name}: ${this.message}\nContext: ${JSON.stringify(this.context, null, 2)}\n${this.stack}`;
}
}
// 2. 堆栈追踪工具
function getStackTrace() {
try {
throw new Error();
} catch (error) {
return error.stack;
}
}
function logCallStack() {
const stack = getStackTrace();
const lines = stack.split('\n').slice(2); // 跳过前两行
console.group('调用栈追踪');
lines.forEach((line, index) => {
if (line.trim()) {
console.log(`${index + 1}: ${line.trim()}`);
}
});
console.groupEnd();
}
// 3. 函数调用追踪
class FunctionTracer {
constructor() {
this.callHistory = [];
}
trace(fn, name) {
const self = this;
return function(...args) {
const callInfo = {
name: name || fn.name,
args: args,
timestamp: Date.now(),
stack: getStackTrace()
};
self.callHistory.push(callInfo);
console.log(`调用: ${callInfo.name}`, args);
try {
const result = fn.apply(this, args);
console.log(`返回: ${callInfo.name}`, result);
return result;
} catch (error) {
console.error(`错误: ${callInfo.name}`, error);
throw error;
}
};
}
getHistory() {
return this.callHistory;
}
clearHistory() {
this.callHistory = [];
}
}
// 使用示例
const tracer = new FunctionTracer();
const tracedFunction = tracer.trace(function add(a, b) {
return a + b;
}, 'addFunction');
tracedFunction(2, 3); // 会记录调用信息
性能调试
性能分析工具
javascript
// 1. 函数性能监控
class PerformanceMonitor {
constructor() {
this.metrics = new Map();
}
measure(fn, name) {
const self = this;
return function(...args) {
const start = performance.now();
const startMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
const result = fn.apply(this, args);
const end = performance.now();
const endMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
const metric = {
name: name || fn.name,
duration: end - start,
memoryChange: endMemory - startMemory,
timestamp: Date.now()
};
if (!self.metrics.has(metric.name)) {
self.metrics.set(metric.name, []);
}
self.metrics.get(metric.name).push(metric);
console.log(`${metric.name} 执行时间: ${metric.duration.toFixed(2)}ms`);
return result;
};
}
getStats(name) {
if (name) {
const metrics = this.metrics.get(name);
if (!metrics) return null;
const durations = metrics.map(m => m.duration);
return {
count: metrics.length,
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
min: Math.min(...durations),
max: Math.max(...durations)
};
}
const allStats = {};
for (const [name, metrics] of this.metrics) {
allStats[name] = this.getStats(name);
}
return allStats;
}
}
// 2. 渲染性能监控
class RenderPerformanceMonitor {
constructor() {
this.observer = null;
this.metrics = [];
}
startMonitoring() {
if ('PerformanceObserver' in window) {
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'measure' || entry.entryType === 'mark') {
this.metrics.push(entry);
}
}
});
this.observer.observe({ entryTypes: ['measure', 'mark', 'paint', 'navigation'] });
}
}
mark(name) {
if ('performance' in window) {
performance.mark(name);
}
}
measure(name, startMark, endMark) {
if ('performance' in window) {
performance.measure(name, startMark, endMark);
}
}
getMetrics() {
return this.metrics;
}
}
调试工具和库
自定义调试工具
javascript
// 1. 状态调试器
class StateDebugger {
constructor(initialState = {}) {
this.state = { ...initialState };
this.history = [];
this.watchers = [];
}
setState(newState) {
const prevState = { ...this.state };
this.state = { ...this.state, ...newState };
// 记录状态变化
const change = {
timestamp: Date.now(),
prevState,
newState,
changes: this.getChanges(prevState, this.state)
};
this.history.push(change);
// 通知观察者
this.watchers.forEach(watcher => watcher(change));
return this.state;
}
getChanges(prevState, newState) {
const changes = {};
const allKeys = new Set([...Object.keys(prevState), ...Object.keys(newState)]);
for (const key of allKeys) {
if (prevState[key] !== newState[key]) {
changes[key] = {
from: prevState[key],
to: newState[key]
};
}
}
return changes;
}
subscribe(watcher) {
this.watchers.push(watcher);
return () => {
this.watchers = this.watchers.filter(w => w !== watcher);
};
}
getHistory() {
return this.history;
}
replay(toIndex) {
if (toIndex < 0 || toIndex >= this.history.length) {
throw new Error('Invalid index');
}
// 恢复到指定状态
this.state = { ...this.history[toIndex].prevState };
return this.state;
}
}
// 2. 函数调用记录器
class FunctionCallLogger {
constructor() {
this.calls = [];
}
log(fn, name) {
const self = this;
return function(...args) {
const callInfo = {
name: name || fn.name,
args: JSON.parse(JSON.stringify(args)), // 深拷贝参数
timestamp: Date.now(),
result: undefined,
error: null
};
try {
const result = fn.apply(this, args);
callInfo.result = result;
self.calls.push(callInfo);
return result;
} catch (error) {
callInfo.error = error.message;
self.calls.push(callInfo);
throw error;
}
};
}
getLogs() {
return this.calls;
}
filterByName(name) {
return this.calls.filter(call => call.name === name);
}
getStats() {
const stats = {};
for (const call of this.calls) {
if (!stats[call.name]) {
stats[call.name] = {
count: 0,
errors: 0,
avgTime: 0
};
}
stats[call.name].count++;
if (call.error) {
stats[call.name].errors++;
}
}
return stats;
}
}
// 使用示例
const callLogger = new FunctionCallLogger();
const loggedAdd = callLogger.log((a, b) => a + b, 'addFunction');
loggedAdd(2, 3);
loggedAdd(5, 7);
console.log(callLogger.getStats());
调试最佳实践
调试策略
javascript
// 1. 渐进式调试方法
class ProgressiveDebugger {
static debug(fn, options = {}) {
const {
logArgs = true,
logResult = true,
logTime = true,
logStack = false
} = options;
return function(...args) {
if (logArgs) {
console.log(`[DEBUG] ${fn.name} called with:`, args);
}
if (logStack) {
console.trace(`[DEBUG] ${fn.name} call stack:`);
}
const startTime = logTime ? performance.now() : null;
try {
const result = fn.apply(this, args);
if (logTime) {
const duration = performance.now() - startTime;
console.log(`[DEBUG] ${fn.name} executed in ${duration.toFixed(2)}ms`);
}
if (logResult) {
console.log(`[DEBUG] ${fn.name} returned:`, result);
}
return result;
} catch (error) {
console.error(`[DEBUG] ${fn.name} threw error:`, error);
throw error;
}
};
}
}
// 2. 条件调试
function conditionalDebug(condition, message, data) {
if (condition) {
console.log(`[CONDITIONAL DEBUG] ${message}:`, data);
}
}
// 使用示例
const debugCondition = location.hostname === 'localhost';
conditionalDebug(debugCondition, 'API Response', { status: 200, data: [...] });
// 3. 调试配置管理
class DebugConfig {
constructor() {
this.config = {
enabled: false,
modules: {},
logLevel: 'info' // 'debug', 'info', 'warn', 'error'
};
// 从URL参数或localStorage加载配置
this.loadConfig();
}
loadConfig() {
// 从URL参数加载
const urlParams = new URLSearchParams(window.location.search);
this.config.enabled = urlParams.get('debug') === 'true';
// 从localStorage加载
const savedConfig = localStorage.getItem('debugConfig');
if (savedConfig) {
try {
const parsed = JSON.parse(savedConfig);
this.config = { ...this.config, ...parsed };
} catch (e) {
console.warn('Invalid debug config in localStorage');
}
}
}
setModuleEnabled(moduleName, enabled) {
this.config.modules[moduleName] = enabled;
this.saveConfig();
}
isEnabled(moduleName = null) {
if (!this.config.enabled) return false;
if (moduleName) {
return this.config.modules[moduleName] !== false;
}
return true;
}
log(moduleName, level, message, data) {
if (!this.isEnabled(moduleName) || this.getLogLevelIndex(level) < this.getLogLevelIndex(this.config.logLevel)) {
return;
}
const prefix = `[${moduleName}]`;
switch (level) {
case 'debug':
console.debug(prefix, message, data);
break;
case 'info':
console.info(prefix, message, data);
break;
case 'warn':
console.warn(prefix, message, data);
break;
case 'error':
console.error(prefix, message, data);
break;
}
}
getLogLevelIndex(level) {
const levels = ['debug', 'info', 'warn', 'error'];
return levels.indexOf(level);
}
saveConfig() {
localStorage.setItem('debugConfig', JSON.stringify(this.config));
}
}
// 全局调试配置实例
const debugConfig = new DebugConfig();
// 使用全局调试配置
function apiCall(url) {
debugConfig.log('api', 'debug', 'Making API call', { url });
return fetch(url)
.then(response => {
debugConfig.log('api', 'debug', 'API response received', {
url,
status: response.status
});
return response;
})
.catch(error => {
debugConfig.log('api', 'error', 'API call failed', { url, error });
throw error;
});
}
常见调试场景
异步调试场景
javascript
// 1. 调试复杂的异步流程
class AsyncFlowDebugger {
constructor() {
this.flowId = 0;
this.flows = new Map();
}
startFlow(name, initialData = {}) {
const id = ++this.flowId;
this.flows.set(id, {
id,
name,
startTime: Date.now(),
steps: [],
data: { ...initialData }
});
console.log(`[ASYNC FLOW] ${name} started (ID: ${id})`);
return id;
}
addStep(flowId, stepName, data = {}) {
const flow = this.flows.get(flowId);
if (!flow) {
console.warn(`Flow ${flowId} not found`);
return;
}
const step = {
name: stepName,
timestamp: Date.now(),
duration: Date.now() - flow.startTime,
data: { ...data }
};
flow.steps.push(step);
console.log(`[ASYNC FLOW] Step "${stepName}" in flow "${flow.name}" (ID: ${flowId})`);
}
completeFlow(flowId, result = null) {
const flow = this.flows.get(flowId);
if (!flow) {
console.warn(`Flow ${flowId} not found`);
return;
}
const totalTime = Date.now() - flow.startTime;
console.log(`[ASYNC FLOW] ${flow.name} completed in ${totalTime}ms (ID: ${flowId})`);
this.flows.delete(flowId);
return { ...flow, result, totalTime };
}
getFlow(flowId) {
return this.flows.get(flowId);
}
}
// 使用异步流程调试器
const asyncDebugger = new AsyncFlowDebugger();
async function complexAsyncOperation(userId) {
const flowId = asyncDebugger.startFlow('user-data-processing', { userId });
try {
asyncDebugger.addStep(flowId, 'fetch-user');
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
asyncDebugger.addStep(flowId, 'fetch-posts', { user });
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
asyncDebugger.addStep(flowId, 'process-data', { postsCount: posts.length });
const processedData = { user, posts, processedAt: new Date() };
return asyncDebugger.completeFlow(flowId, processedData);
} catch (error) {
console.error('Async operation failed:', error);
asyncDebugger.completeFlow(flowId, { error: error.message });
throw error;
}
}
调试是JavaScript开发中的重要技能,通过合理使用工具和技巧,可以快速定位问题并提高开发效率。