浏览器开发者工具
Console 面板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| console.log('普通日志'); console.info('信息日志'); console.warn('警告日志'); console.error('错误日志');
console.log('字符串: %s', 'hello'); console.log('数字: %d', 42); console.log('对象: %o', { name: '张三' }); console.log('CSS 样式: %c自定义样式', 'color: red; font-size: 20px;');
const users = [ { id: 1, name: '张三', age: 25 }, { id: 2, name: '李四', age: 30 }, { id: 3, name: '王五', age: 28 }, ]; console.table(users);
console.table(users, ['name', 'age']);
console.group('用户信息'); console.log('姓名: 张三'); console.log('年龄: 25'); console.groupCollapsed('详细信息'); console.log('邮箱: zhangsan@example.com'); console.log('电话: 1234567890'); console.groupEnd(); console.groupEnd();
console.time('操作耗时');
for (let i = 0; i < 1000000; i++) { } console.timeEnd('操作耗时');
console.time('定时器'); setTimeout(() => { console.timeLog('定时器'); }, 1000); setTimeout(() => { console.timeEnd('定时器'); }, 2000);
function processItem(item) { console.count(`处理 ${item}`); }
processItem('A'); processItem('B'); processItem('A');
function divide(a, b) { console.assert(b !== 0, '除数不能为零'); return a / b; }
divide(10, 0);
function trace() { console.trace('调用栈追踪'); }
function a() { trace(); }
function b() { a(); }
b();
console.clear();
console.dir(document.body); console.dir(document.body, { depth: null });
console.dirxml(document.body);
|
条件断点与日志断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
|
网络面板技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
console.time('自定义标记');
console.timeEnd('自定义标记');
performance.mark('start');
performance.mark('end'); performance.measure('操作', 'start', 'end');
|
Memory 面板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
const weakRef = new WeakRef(obj); const deref = weakRef.deref(); if (deref) { console.log('对象还存在'); } else { console.log('对象已被回收'); }
const registry = new FinalizationRegistry((id) => { console.log(`对象 ${id} 已被回收`); });
registry.register(obj, 'obj-1');
|
Firefox 的开发者工具在某些方面比 Chrome 更强大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
|
调试技巧
条件调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function processData(data) { if (data.length > 100) { debugger; } return data.map(item => transform(item)); }
function debugIf(condition) { if (condition) { debugger; } }
function complexFunction(user) { debugIf(user.id === 123); }
|
时间旅行调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import { produce } from 'immer';
class TimeTravelDebugger { private history: any[] = []; private currentIndex = -1; record(state: any) { this.history = this.history.slice(0, this.currentIndex + 1); this.history.push(JSON.parse(JSON.stringify(state))); this.currentIndex++; } undo() { if (this.currentIndex > 0) { this.currentIndex--; return this.history[this.currentIndex]; } return null; } redo() { if (this.currentIndex < this.history.length - 1) { this.currentIndex++; return this.history[this.currentIndex]; } return null; } goTo(index: number) { if (index >= 0 && index < this.history.length) { this.currentIndex = index; return this.history[this.currentIndex]; } return null; } }
const debugger = new TimeTravelDebugger();
let state = { count: 0 }; debugger.record(state);
state = produce(state, draft => { draft.count = 1; }); debugger.record(state);
state = produce(state, draft => { draft.count = 2; }); debugger.record(state);
console.log(debugger.undo()); console.log(debugger.undo()); console.log(debugger.redo());
|
异步调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| async function debugAsync() { try { console.log('开始'); const result1 = await fetchData1(); debugger; const result2 = await fetchData2(result1); debugger; return result2; } catch (error) { debugger; throw error; } }
function debugPromiseChain() { return fetchData1() .then(result1 => { debugger; return fetchData2(result1); }) .then(result2 => { debugger; return result2; }) .catch(error => { debugger; throw error; }); }
window.addEventListener('unhandledrejection', event => { console.error('未处理的 Promise 拒绝:', event.reason); debugger; });
|
网络请求调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| const originalXHR = window.XMLHttpRequest;
function debugXHR() { const xhr = new originalXHR(); const originalOpen = xhr.open; const originalSend = xhr.send; xhr.open = function(method, url, ...args) { console.log(`XHR 请求: ${method} ${url}`); return originalOpen.apply(this, [method, url, ...args]); }; xhr.send = function(data) { console.log('XHR 发送数据:', data); this.addEventListener('load', () => { console.log(`XHR 响应: ${this.status}`, this.responseText); }); this.addEventListener('error', () => { console.error('XHR 错误'); debugger; }); return originalSend.apply(this, [data]); }; return xhr; }
const originalFetch = window.fetch;
window.fetch = async function(...args) { const [url, options] = args; console.log(`Fetch 请求: ${url}`, options); try { const response = await originalFetch.apply(this, args); const clone = response.clone(); clone.text().then(text => { console.log(`Fetch 响应: ${url}`, text); }); return response; } catch (error) { console.error(`Fetch 错误: ${url}`, error); debugger; throw error; } };
async function fetchWithDelay(url: string, delay: number = 1000) { await new Promise(resolve => setTimeout(resolve, delay)); return fetch(url); }
function fetchWithError(url: string, shouldFail: boolean = false) { if (shouldFail) { return Promise.reject(new Error('模拟网络错误')); } return fetch(url); }
|
DOM 调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { console.log('DOM 变化:', mutation); console.log('类型:', mutation.type); console.log('目标:', mutation.target); if (mutation.type === 'childList') { console.log('添加的节点:', mutation.addedNodes); console.log('移除的节点:', mutation.removedNodes); } if (mutation.type === 'attributes') { console.log('属性名:', mutation.attributeName); } debugger; }); });
observer.observe(document.body, { childList: true, attributes: true, subtree: true, characterData: true, });
function findElements() { const elements = document.querySelectorAll('.my-class'); const xpathResult = document.evaluate( '//div[@class="my-class"]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); const fastElements = document.getElementsByClassName('my-class'); function isVisible(element: HTMLElement) { const style = window.getComputedStyle(element); return ( style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && element.offsetWidth > 0 && element.offsetHeight > 0 ); } function isInViewport(element: HTMLElement) { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth ); } }
function highlightElement(element: HTMLElement, color: string = 'red') { const originalOutline = element.style.outline; element.style.outline = `3px solid ${color}`; setTimeout(() => { element.style.outline = originalOutline; }, 2000); }
|
错误处理与追踪
全局错误捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| window.addEventListener('error', (event) => { console.error('全局错误:', { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error, }); sendToErrorTracking({ type: 'javascript', message: event.message, stack: event.error?.stack, url: event.filename, line: event.lineno, column: event.colno, timestamp: Date.now(), userAgent: navigator.userAgent, url: window.location.href, }); });
window.addEventListener('unhandledrejection', (event) => { console.error('未处理的 Promise 拒绝:', event.reason); sendToErrorTracking({ type: 'promise', message: event.reason?.message || String(event.reason), stack: event.reason?.stack, timestamp: Date.now(), }); });
window.addEventListener('error', (event) => { const target = event.target as HTMLElement; if (target.tagName) { console.error('资源加载失败:', { tag: target.tagName, src: (target as HTMLImageElement).src || (target as HTMLLinkElement).href, }); } }, true);
function sendToErrorTracking(data: any) { const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); navigator.sendBeacon('/api/error-tracking', blob); }
|
Source Maps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| export default defineConfig({ build: { sourcemap: true, sourcemap: 'hidden', }, });
import * as Sentry from '@sentry/browser';
Sentry.init({ dsn: 'your-dsn-here', environment: process.env.NODE_ENV, release: '1.0.0', });
|
错误追踪服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import * as Sentry from '@sentry/browser'; import { BrowserTracing } from '@sentry/tracing';
Sentry.init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', integrations: [new BrowserTracing()], tracesSampleRate: 1.0, environment: process.env.NODE_ENV, release: '1.0.0', beforeSend(event) { if (event.request?.headers) { delete event.request.headers['Authorization']; } return event; }, setUser(user) { Sentry.setUser({ id: user.id, username: user.username, email: user.email, }); }, setTag(key: string, value: string) { Sentry.setTag(key, value); }, addBreadcrumb(breadcrumb) { Sentry.addBreadcrumb({ message: breadcrumb.message, category: breadcrumb.category, level: breadcrumb.level, data: breadcrumb.data, }); }, });
try { } catch (error) { Sentry.captureException(error); }
Sentry.captureMessage('这是一条消息', 'info');
const transaction = Sentry.startTransaction({ name: '自定义事务', });
const span = transaction.startChild({ op: 'http', description: 'GET /api/users', });
await fetch('/api/users');
span.finish(); transaction.finish();
|
日志管理
分级日志系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, }
class Logger { private level: LogLevel; private prefix: string; constructor(prefix: string = '', level: LogLevel = LogLevel.INFO) { this.prefix = prefix; this.level = level; } private shouldLog(level: LogLevel): boolean { return level >= this.level; } private formatMessage(level: string, message: string): string { const timestamp = new Date().toISOString(); return `[${timestamp}] [${level}] [${this.prefix}] ${message}`; } debug(message: string, ...args: any[]) { if (this.shouldLog(LogLevel.DEBUG)) { console.debug(this.formatMessage('DEBUG', message), ...args); } } info(message: string, ...args: any[]) { if (this.shouldLog(LogLevel.INFO)) { console.info(this.formatMessage('INFO', message), ...args); } } warn(message: string, ...args: any[]) { if (this.shouldLog(LogLevel.WARN)) { console.warn(this.formatMessage('WARN', message), ...args); } } error(message: string, ...args: any[]) { if (this.shouldLog(LogLevel.ERROR)) { console.error(this.formatMessage('ERROR', message), ...args); } } setLevel(level: LogLevel) { this.level = level; } }
const logger = new Logger('App', LogLevel.DEBUG);
logger.debug('调试信息'); logger.info('一般信息'); logger.warn('警告信息'); logger.error('错误信息');
const apiLogger = new Logger('API'); const uiLogger = new Logger('UI');
apiLogger.info('发送请求'); uiLogger.info('渲染组件');
|
远程日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| class RemoteLogger { private buffer: any[] = []; private flushInterval: number = 5000; private maxBufferSize: number = 100; private timer: NodeJS.Timeout | null = null; constructor() { this.startFlushTimer(); } private startFlushTimer() { this.timer = setInterval(() => { this.flush(); }, this.flushInterval); } private addLog(level: string, message: string, data?: any) { const log = { timestamp: Date.now(), level, message, data, url: window.location.href, userAgent: navigator.userAgent, }; this.buffer.push(log); if (this.buffer.length >= this.maxBufferSize) { this.flush(); } } private async flush() { if (this.buffer.length === 0) return; const logs = [...this.buffer]; this.buffer = []; try { await fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ logs }), }); } catch (error) { this.buffer.unshift(...logs); const blob = new Blob([JSON.stringify({ logs })], { type: 'application/json', }); navigator.sendBeacon('/api/logs', blob); } } info(message: string, data?: any) { this.addLog('info', message, data); } warn(message: string, data?: any) { this.addLog('warn', message, data); } error(message: string, data?: any) { this.addLog('error', message, data); } destroy() { if (this.timer) { clearInterval(this.timer); } this.flush(); } }
const remoteLogger = new RemoteLogger();
window.addEventListener('beforeunload', () => { remoteLogger.destroy(); });
|
性能分析工具
Lighthouse CI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ci: collect: url: - 'http://localhost:3000' - 'http://localhost:3000/about' numberOfRuns: 3 settings: preset: 'desktop' assert: assertMatrix: - matchingUrlPattern: '.*' assertions: 'categories:performance': ['error', { minScore: 0.9 }] 'categories:accessibility': ['error', { minScore: 0.9 }] 'categories:best-practices': ['error', { minScore: 0.9 }] 'categories:seo': ['error', { minScore: 0.9 }] upload: target: 'lhci' serverBaseUrl: 'http://localhost:9001' token: 'your-token'
|
Web Vitals 监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { onCLS, onFID, onLCP, onINP, onTTFB } from 'web-vitals';
function sendToAnalytics(name: string, value: number, id: string) { if (typeof gtag !== 'undefined') { gtag('event', name, { event_category: 'Web Vitals', event_value: Math.round(name === 'CLS' ? value * 1000 : value), event_label: id, non_interaction: true, }); } }
onCLS((metric) => sendToAnalytics('CLS', metric.value, metric.id)); onFID((metric) => sendToAnalytics('FID', metric.value, metric.id)); onLCP((metric) => sendToAnalytics('LCP', metric.value, metric.id)); onINP((metric) => sendToAnalytics('INP', metric.value, metric.id)); onTTFB((metric) => sendToAnalytics('TTFB', metric.value, metric.id));
|
调试最佳实践
结构化调试流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
|
有效的调试注释
1 2 3 4 5 6 7 8 9
|
console.log('调试信息');
|
调试工具推荐
浏览器扩展:
- React Developer Tools
- Vue.js DevTools
- Redux DevTools
- ColorZilla
- WhatFont
- Page Ruler
独立工具:
- Charles Proxy - 网络调试代理
- Fiddler - Windows 网络调试工具
- Postman - API 测试
- Insomnia - API 测试
性能分析:
- Lighthouse
- WebPageTest
- GTmetrix
- PageSpeed Insights
错误追踪:
- Sentry
- Bugsnag
- Rollbar
- LogRocket