0%

浏览器调试功能入门——包括断点、快捷键等调试技巧

最近在带新人,发现很多人对浏览器调试工具不够熟悉,导致很多简单问题调试起来很费劲。花了点时间整理了一下常用的调试技巧,熟练使用调试工具绝对是个硬技能,能节省大量开发时间。

浏览器调试工具简介

  现代浏览器都内置了强大的开发者工具(Developer Tools),其中 Chrome DevTools 是最受欢迎的调试工具之一。掌握调试工具的使用不仅能帮助我们快速定位代码问题,还能优化性能、分析网络请求、检查页面元素等。

  调试工具通常包括以下主要面板:

  1. Elements: 查看和编辑 Html/Css
  2. Console: 执行 Javascript 代码和查看日志
  3. Sources: 调试 Javascript 代码
  4. Network: 监控网络请求
  5. Performance: 性能分析
  6. Memory: 内存分析
  7. Application: 查看存储和应用数据

Console 面板详解

基础输出方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基本输出 console.log('普通信息');
console.warn('警告信息');
console.error('错误信息');
console.info('提示信息');

// 格式化输出 console.log('用户%s 年龄%d 岁', '张三', 25);
console.log('价格: %f', 99.99);

// 对象输出 const user = { name: '张三', age: 25 };
console.log('用户信息:', user);
console.table([{ name: '张三', age: 25 }, { name: '李四', age: 30 }]);

// 分组输出 console.group('用户详情');
console.log('姓名: 张三');
console.log('年龄: 25');
console.groupEnd();

条件输出和计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 条件输出 const count = 10;
console.assert(count > 10, '计数不应该超过10');

// 计数器 for (let i = 0; i < 5; i++) {
console.count('循环次数');
}

// 时间测量 console.time('数组操作');
const arr = new Array(1000000).fill(0).map((_, i) => i);
console.timeEnd('数组操作');

// 性能测量 console.time('复杂计算');
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
console.timeEnd('复杂计算');

实用调试技巧

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
// 1. 使用 console.trace()查看调用栈 function functionA() {
functionB();
}

function functionB() {
functionC();
}

function functionC() {
console.trace('调用栈信息');
}

functionA();

// 2. 使用 console.dir()查看对象属性 const obj = {
name: '张三',
details: {
age: 25,
city: '北京'
}
};
console.dir(obj);

// 3. 使用 console.groupCollapsed()创建可折叠分组 console.groupCollapsed('详细信息');
console.log('这里是详细信息');
console.log('只有展开才能看到');
console.groupEnd();

// 4. 清空控制台
// console.clear();

// 5. 使用 Css 样式美化输出 console.log('%c 重要信息', 'color: red; font-size: 20px; font-weight: bold;');
console.log('%c 普通信息', 'color: blue; background: yellow; padding: 2px;');

Sources 面板 - 断点调试

基础断点设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 示例代码用于演示断点调试 function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) { // 在此行设置断点 const item = items[i]; // 在此行设置断点 total += item.price * item.quantity; // 在此行设置断点
}
return total; // 在此行设置断点
}

const products = [
{ name: '苹果', price: 5, quantity: 2 },
{ name: '香蕉', price: 3, quantity: 3 },
{ name: '橙子', price: 4, quantity: 1 }
];

const result = calculateTotal(products);
console.log('总计:', result);

断点类型详解

1. 普通断点(Line Breakpoints)

  最常用的断点类型,当代码执行到指定行时暂停。在 Sources 面板的行号左侧点击即可设置。

2. 条件断点(Conditional Breakpoints)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 条件断点示例 function processUsers(users) {
for (let i = 0; i < users.length; i++) {
const user = users[i];

// 在此行设置条件断点: user.age > 30
console.log('处理用户:', user.name);

if (user.active) {
// 在此行设置条件断点: user.name === '张三'
console.log('激活用户:', user.name);
}
}
}

const users = [
{ name: '张三', age: 25, active: true },
{ name: '李四', age: 35, active: false },
{ name: '王五', age: 28, active: true }
];

processUsers(users);

3. 日志断点(Logpoints)

  不中断执行,只在控制台输出信息的特殊断点。右键点击行号,选择”Add logpoint”。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Logpoint 示例: 输出 { user.name } + " 被处理"
function processUser(user) {
console.log('处理用户信息');
// 在此行添加 Logpoint: { user.name } + " 开始处理"

const processed = {
name: user.name.toUpperCase(),
age: user.age + 1
};

// 在此行添加 Logpoint: { processed.name } + " 处理完成"
return processed;
}

高级断点技巧

DOM 断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE Html>
<Html>
<head>
<title>DOM 断点示例</title>
</head>
<body>
<div id="target">点击我</div>
<button onclick="modifyTarget()">修改目标</button>

<script>
function modifyTarget() {
const target = document.getElementById('target');
target.textContent = '已被修改';
target.style.color = 'red';
}
</script>
</body>
</Html>

  在 Elements 面板中右键点击 DOM 元素,可以选择”Break on”来设置 DOM 断点:

  • Subtree modifications: 子节点发生变化时中断
  • Attributes modifications: 属性发生变化时中断
  • Node removal: 节点被移除时中断

事件监听器断点

1
2
3
4
5
6
7
// 事件监听器断点示例 document.addEventListener('click', function(event) {
console.log('页面被点击:', event.target);
});

document.getElementById('target').addEventListener('click', function(event) {
console.log('目标被点击:', event.target);
});

  在 Sources 面板的”Event Listener Breakpoints”部分,可以勾选要监听的事件类型。

调试控制技巧

调试器控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function complexFunction() {
debugger; // 代码中也可以直接使用 debugger 语句设置断点 let result = 0;

for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
result += i;
} else {
result *= i;
}
}

return result;
}

complexFunction();

调试步骤控制

在断点暂停时,可以使用以下控制按钮:

  1. Resume/Pause (F8): 继续执行或暂停
  2. Step Over (F10): 单步执行,不进入函数内部
  3. Step Into (F11): 进入函数内部执行
  4. Step Out (Shift+F11): 从当前函数中跳出
  5. Deactivate breakpoints: 禁用所有断点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 调试步骤演示 function outerFunction() {
console.log('外层函数开始');
innerFunction(); // Step Into 会进入此函数 console.log('外层函数结束');
}

function innerFunction() {
console.log('内层函数执行');
const result = calculateValue(); // Step Into 会进入此函数 return result;
}

function calculateValue() {
return 42;
}

outerFunction();

Watch 表达式和变量监视

监视表达式

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
function processData(data) {
let processed = 0;
let errors = 0;
const results = [];

for (let i = 0; i < data.length; i++) {
const item = data[i];

// 在 Watch 面板中添加以下表达式:
// - processed
// - errors
// - results.length
// - item.name
// - item.value > 100

if (item.value > 100) {
processed++;
results.push(item);
} else {
errors++;
}
}

return { processed, errors, results };
}

const testData = [
{ name: 'A', value: 150 },
{ name: 'B', value: 50 },
{ name: 'C', value: 200 }
];

processData(testData);

调用栈分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 调用栈分析示例 function level3() {
console.trace('当前调用栈'); // 也可以在控制台查看
// 在断点处可以查看 Call Stack 面板 return 'level3 result';
}

function level2() {
const result = level3();
return `level2 got ${result}`;
}

function level1() {
const result = level2();
return `level1 got ${result}`;
}

level1();

Network 面板调试

网络请求监控

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
// 网络请求示例 async function fetchData() {
try {
// 在 Network 面板可以监控以下请求 const response = await fetch('/API/users', {
method: 'GET',
headers: {
'Content-Type': 'application/Json',
'Authorization': 'Bearer token123'
}
});

const data = await response.Json();
console.log('获取数据:', data);
} catch (error) {
console.error('请求失败:', error);
}
}

async function postData() {
try {
const response = await fetch('/API/users', {
method: 'POST',
headers: {
'Content-Type': 'application/Json'
},
body: Json.stringify({
name: '张三',
email: 'zhangsan@example.com'
})
});

const result = await response.Json();
console.log('提交结果:', result);
} catch (error) {
console.error('提交失败:', error);
}
}

网络条件模拟

在 Network 面板可以模拟不同的网络条件:

  • Online: 正常网络
  • Fast 3G: 慢速3G 网络
  • Slow 3G: 极慢3G 网络
  • Offline: 离线状态

Performance 面板使用

性能分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 性能分析示例 function performanceIntensiveFunction() {
// 在 Performance 面板录制时可以分析以下代码的性能 const startTime = performance.now();

// 模拟大量计算 let result = 0;
for (let i = 0; i < 10000000; i++) {
result += Math.sqrt(i) * Math.sin(i);
}

// 模拟 DOM 操作 const container = document.createElement('div');
for (let i = 0; i < 1000; i++) {
const item = document.createElement('div');
item.textContent = `Item ${i}`;
container.appendChild(item);
}

const endTime = performance.now();
console.log(`执行时间: ${endTime - startTime}ms`);

return result;
}

performanceIntensiveFunction();

调试快捷键汇总

Chrome DevTools 常用快捷键

操作Windows/LinuxMac
打开 DevToolsF12 或 Ctrl+Shift+ICmd+Option+I
打开 Elements 面板Ctrl+Shift+CCmd+Shift+C
打开 ConsoleCtrl+Shift+JCmd+Option+J
打开 Sources 面板Ctrl+Shift+SCmd+Shift+S
打开 Network 面板Ctrl+Shift+QCmd+Shift+Q
打开 Performance 面板Ctrl+Shift+PCmd+Shift+P
继续执行F8F8
单步执行F10F10
进入函数F11F11
跳出函数Shift+F11Shift+F11
重新加载Ctrl+RCmd+R
硬刷新Ctrl+Shift+RCmd+Shift+R
搜索文件Ctrl+PCmd+P
搜索内容Ctrl+Shift+FCmd+Shift+F
跳转到行Ctrl+GCmd+G
切换面板Ctrl+[1-9]Cmd+[1-9]

Console 快捷键

操作快捷键
执行命令Enter
新行Shift+Enter
历史向上Up Arrow
历史向下Down Arrow
清空控制台Ctrl+L

实际调试案例

案例1: 调试异步代码

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
// 异步代码调试示例 async function fetchUserData(userId) {
console.log('开始获取用户数据:', userId);

try {
// 设置断点观察异步执行过程 const response = await fetch(`/API/users/${userId}`);

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const userData = await response.Json();
console.log('用户数据获取成功:', userData);

// 可能在数据处理中有 bug
const processedData = processUser(userData);
return processedData;

} catch (error) {
console.error('获取用户数据失败:', error);
throw error;
}
}

function processUser(user) {
// 假设这里有 bug
const fullName = user.firstName + ' ' + user.lastName; // 如果 firstName 或 lastName 不存在会出错 return {
id: user.id,
name: fullName,
email: user.email?.toLowerCase() // email 可能为 null
};
}

// 调试步骤:
// 1. 在 fetch 行设置断点
// 2. 观察 response 状态
// 3. 在 processUser 函数中设置断点
// 4. 检查 user 对象结构
// 5. 观察变量值变化

案例2: 调试性能问题

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
// 性能问题调试示例 function findDuplicates(arr) {
// 潜在的性能问题:O(n²)复杂度 const duplicates = [];

for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j] && !duplicates.includes(arr[i])) {
duplicates.push(arr[i]);
}
}
}

return duplicates;
}

function findDuplicatesOptimized(arr) {
// 优化版本:O(n)复杂度 const seen = new Set();
const duplicates = new Set();

for (const item of arr) {
if (seen.has(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}

return Array.from(duplicates);
}

// 性能对比测试 const largeArray = Array.from({ length: 10000 }, () => Math.floor(Math.random() * 5000));

console.time('未优化版本');
const result1 = findDuplicates(largeArray);
console.timeEnd('未优化版本');

console.time('优化版本');
const result2 = findDuplicatesOptimized(largeArray);
console.timeEnd('优化版本');

案例3: 调试内存泄漏

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
// 内存泄漏示例 class MemoryLeakExample {
constructor() {
this.data = [];
this.intervalId = null;
this.element = document.getElementById('target');
}

startLeaking() {
// 潜在的内存泄漏:持续添加数据且不清理 this.intervalId = setInterval(() => {
// 添加大量数据 for (let i = 0; i < 1000; i++) {
this.data.push({
id: i,
timestamp: Date.now(),
data: new Array(100).fill(Math.random())
});
}

// 保持对 DOM 元素的引用 if (this.element) {
this.element.textContent = `数据量: ${this.data.length}`;
}
}, 100);
}

stopLeaking() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}

// 修复内存泄漏的方法 cleanup() {
this.stopLeaking();
this.data = [];
this.element = null;
}
}

// 调试内存泄漏:
// 1. 使用 Performance 面板录制
// 2. 使用 Memory 面板拍照对比
// 3. 观察内存使用趋势

调试最佳实践

1. 系统化调试方法

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
// 调试前的准备 function debugSystematic() {
// 1. 明确问题现象 const problemDescription = {
what: '页面加载缓慢',
when: '在用户登录后',
where: '首页',
who: '特定用户群'
};

// 2. 复现问题 const reproductionSteps = [
'登录用户 A',
'导航到首页',
'等待30秒观察'
];

// 3. 分析可能原因 const possibleCauses = [
'API 请求慢',
'大量 DOM 操作',
'内存泄漏',
'第三方脚本问题'
];

// 4. 制定调试计划 const debuggingPlan = {
network: '检查 Network 面板',
performance: '录制 Performance',
console: '检查错误日志',
memory: '分析 Memory 使用'
};
}

2. 调试信息记录

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
// 创建调试助手类 class DebugHelper {
constructor() {
this.debugMode = true;
this.logHistory = [];
}

log(message, data = null) {
if (!this.debugMode) return;

const logEntry = {
timestamp: new Date().toISOString(),
message,
data,
stack: new Error().stack
};

this.logHistory.push(logEntry);
console.log(`[DEBUG] ${message}`, data);
}

error(message, error) {
console.error(`[ERROR] ${message}`, error);
this.logHistory.push({
timestamp: new Date().toISOString(),
message,
error: error.message,
stack: error.stack
});
}

// 获取调试历史 getHistory() {
return this.logHistory;
}

// 导出调试信息 exportDebugInfo() {
const debugInfo = {
timestamp: new Date().toISOString(),
browser: navigator.userAgent,
history: this.logHistory,
performance: performance.memory || 'Not available'
};

console.log('Debug Info Exported:', debugInfo);
return Json.stringify(debugInfo, null, 2);
}
}

// 使用示例 const debugHelper = new DebugHelper();
debugHelper.log('应用启动', { version: '1.0.0' });

3. 调试工具配置

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
// 调试配置 const debugConfig = {
// 控制台设置 console: {
enableLogging: true,
logLevel: 'info', // 'debug', 'info', 'warn', 'error'
maxHistory: 1000
},

// 断点设置 breakpoints: {
enableConditional: true,
maxConditions: 10,
autoDisable: false
},

// 性能监控 performance: {
recordLongTasks: true,
recordFrames: true,
sampleInterval: 1000
},

// 网络监控 network: {
recordHeaders: true,
recordCookies: true,
timeoutWarning: 5000
}
};

总结

  • 掌握 Console 面板的基本和高级功能
  • 熟练使用各种断点类型进行代码调试
  • 了解 Sources 面板的高级调试技巧
  • 学会使用 Network 面板监控网络请求
  • 掌握 Performance 面板进行性能分析
  • 熟悉常用的调试快捷键
  • 培养系统化的调试思维

刚入行的时候,遇到 bug 总是慌,现在学会了系统化调试,发现大部分问题都能快速定位解决。调试不仅是个技术活,更是个思维活。好的调试习惯能让你的开发效率翻倍。

扩展阅读

  • Chrome DevTools Documentation
  • Firefox Developer Tools
  • Javascript Debugging Best Practices
  • Browser Developer Tools Guide
  • Performance Optimization Techniques

参考资料

  • Chrome DevTools: https://developers.google.com/web/tools/chrome-devtools
  • Firefox DevTools: https://developer.mozilla.org/en-US/docs/Tools
  • Safari Web Inspector: https://support.apple.com/en-us/guide/safari-developer
  • Microsoft Edge DevTools: https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium
bulb