欢迎使用我的小程序👇👇👇👇👇
引言
在前几篇文章中,我们学习了JavaScript的基础、ES6特性、数组对象操作和异步编程。本文将深入探讨JavaScript开发中不可或缺的重要环节:错误处理与调试。这是成为优秀JavaScript开发者的关键技能。
一、JavaScript错误类型
1.1 内置错误类型
// SyntaxError: 语法错误
// let 1name = "John"; // 报错:变量名不能以数字开头
// ReferenceError: 引用错误
// console.log(undefinedVariable); // 报错:变量未定义
// TypeError: 类型错误
// const num = 123;
// num.toUpperCase(); // 报错:数字没有toUpperCase方法
// RangeError: 范围错误
// const arr = new Array(-1); // 报错:数组长度不能为负数
// URIError: URI相关错误
// decodeURIComponent('%'); // 报错:URI格式不正确
// EvalError: eval函数错误(现代JavaScript中较少见)
1.2 自定义错误类型
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
this.timestamp = new Date().toISOString();
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
// 使用自定义错误
function validateUser(user) {
if (!user.name) {
throw new ValidationError("用户名不能为空", "name");
}
if (user.age < 0) {
throw new ValidationError("年龄不能为负数", "age");
}
}
二、错误处理机制
2.1 try...catch...finally
// 基本用法
function safeDivision(a, b) {
try {
console.log(`开始计算: ${a} ÷ ${b}`);
if (b === 0) {
throw new Error("除数不能为零");
}
const result = a / b;
console.log(`计算结果: ${result}`);
return result;
} catch (error) {
console.error("计算发生错误:", {
message: error.message,
name: error.name,
stack: error.stack.split('\n').slice(0, 3).join('\n') // 只显示前3行调用栈
});
return null;
} finally {
console.log("计算过程结束,清理资源");
// finally块总是会执行,无论是否发生错误
}
}
safeDivision(10, 2);
safeDivision(10, 0);
2.2 异步错误处理
// Promise的错误处理
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new NetworkError(
`HTTP错误! 状态码: ${response.status}`,
response.status
);
}
const data = await response.json();
return data;
} catch (error) {
if (error instanceof NetworkError) {
console.error("网络请求错误:", error.message);
// 重试逻辑
return await retryFetch(userId);
} else {
console.error("未知错误:", error);
throw error; // 重新抛出错误
}
}
}
// Promise链的错误处理
fetchUserData(123)
.then(data => console.log("用户数据:", data))
.catch(error => console.error("获取用户数据失败:", error))
.finally(() => console.log("请求完成"));
// async/await中的错误处理优化
async function processMultipleRequests(ids) {
const results = await Promise.allSettled(
ids.map(id => fetchUserData(id).catch(error => ({
error,
id,
status: 'failed'
})))
);
const successes = results.filter(r => r.status === 'fulfilled');
const failures = results.filter(r => r.status === 'rejected');
console.log(`成功: ${successes.length}, 失败: ${failures.length}`);
return { successes, failures };
}
三、浏览器调试工具详解
3.1 Chrome DevTools 高级调试技巧
// 调试示例函数
function complexCalculation(data) {
// 1. 使用debugger语句设置断点
// debugger;
console.group("复杂计算开始");
// 2. 条件断点(在DevTools中设置)
// 在data.length > 100时中断
const processed = data.map((item, index) => {
// 3. 日志点(在DevTools中设置)
const result = item.value * Math.sqrt(index);
// 4. 监视表达式(在DevTools中设置)
// index === 50
// item.value > threshold
return {
...item,
result,
normalized: result / 100
};
});
console.table(processed.slice(0, 5)); // 显示前5条数据
console.groupEnd();
return processed;
}
// 性能调试
function performanceSensitiveFunction() {
console.time("性能测试");
// 使用Performance面板记录性能
const results = [];
for (let i = 0; i < 1000000; i++) {
// 避免在循环中创建函数
results.push(Math.sqrt(i) * Math.random());
}
console.timeEnd("性能测试");
console.profileEnd(); // 与console.profile()配对使用
return results;
}
3.2 Source Map与源代码调试
// 配置webpack source map
// webpack.config.js:
/*
module.exports = {
devtool: 'source-map', // 开发环境推荐
// devtool: 'cheap-module-source-map', // 生产环境推荐
// ...
};
*/
// 使用eval进行动态代码调试(谨慎使用)
function dynamicCodeEvaluation(codeString) {
try {
// 使用严格模式的eval
const result = (function() {
"use strict";
return eval(codeString);
})();
return {
success: true,
result
};
} catch (error) {
console.error("动态代码执行错误:", {
code: codeString,
error: error.message,
stack: error.stack
});
return {
success: false,
error: error.message
};
}
}
四、实用调试技巧与模式
4.1 错误追踪与日志记录
class Logger {
constructor(context = "App") {
this.context = context;
this.levels = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3
};
this.currentLevel = this.levels.DEBUG;
}
log(level, message, data = {}) {
if (this.levels[level] >= this.currentLevel) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
context: this.context,
message,
data,
userAgent: navigator?.userAgent || "Node.js"
};
// 控制台输出
const colors = {
DEBUG: 'color: gray',
INFO: 'color: blue',
WARN: 'color: orange',
ERROR: 'color: red'
};
console.log(
`%c[${timestamp}] ${level}: ${message}`,
colors[level],
data
);
// 发送到服务器(在生产环境中)
if (level === 'ERROR' && process.env.NODE_ENV === 'production') {
this.sendToServer(logEntry);
}
}
}
debug(message, data) {
this.log('DEBUG', message, data);
}
error(message, error, extraData = {}) {
const errorData = {
message: error.message,
name: error.name,
stack: error.stack,
...extraData
};
this.log('ERROR', message, errorData);
}
async sendToServer(logEntry) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
});
} catch (e) {
console.error('日志发送失败:', e);
}
}
}
// 使用示例
const logger = new Logger("UserService");
async function getUserProfile(userId) {
logger.debug("获取用户资料", { userId });
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
logger.error("获取用户资料失败", error, { userId });
throw error;
}
}
4.2 防御性编程模式
// 1. 参数验证
function processOrder(order, options = {}) {
// 参数类型检查
if (!order || typeof order !== 'object') {
throw new TypeError('order参数必须是对象');
}
// 必需字段检查
const requiredFields = ['id', 'items', 'total'];
for (const field of requiredFields) {
if (!order.hasOwnProperty(field)) {
throw new ValidationError(`订单缺少必需字段: ${field}`, field);
}
}
// 默认值设置
const config = {
validate: true,
logLevel: 'info',
timeout: 5000,
...options
};
// 范围检查
if (order.total < 0) {
throw new RangeError('订单总金额不能为负数');
}
// 安全处理
try {
return validateAndProcess(order, config);
} catch (error) {
// 错误恢复策略
if (config.fallback) {
return config.fallback(order);
}
throw error;
}
}
// 2. 数据验证函数
const Validators = {
isEmail: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
isPhone: (phone) => /^1[3-9]\d{9}$/.test(phone),
isStrongPassword: (password) => password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/\d/.test(password),
validateObject: (obj, schema) => {
const errors = [];
for (const [key, validator] of Object.entries(schema)) {
try {
validator(obj[key]);
} catch (error) {
errors.push({
field: key,
error: error.message,
value: obj[key]
});
}
}
return errors.length === 0 ? null : errors;
}
};
// 3. 重试机制
async function withRetry(fn, options = {}) {
const {
maxAttempts = 3,
delay = 1000,
backoff = true,
shouldRetry = () => true
} = options;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (attempt === maxAttempts || !shouldRetry(error)) {
break;
}
const waitTime = backoff ? delay * Math.pow(2, attempt - 1) : delay;
console.warn(`第${attempt}次尝试失败,${waitTime}ms后重试`, error.message);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
throw lastError;
}
// 使用示例
async function fetchWithRetry(url) {
return await withRetry(
() => fetch(url).then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}),
{
maxAttempts: 5,
delay: 1000,
shouldRetry: (error) => !error.message.includes('404') // 404错误不重试
}
);
}
4.3 性能监控与错误上报
// 全局错误监听
window.addEventListener('error', (event) => {
const { message, filename, lineno, colno, error } = event;
const errorReport = {
type: 'unhandledError',
message,
filename,
position: `${lineno}:${colno}`,
stack: error?.stack,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent
};
// 发送到错误监控服务
sendErrorReport(errorReport);
// 防止错误再次抛出(可选)
event.preventDefault();
});
// Promise未捕获错误
window.addEventListener('unhandledrejection', (event) => {
const errorReport = {
type: 'unhandledRejection',
reason: event.reason?.message || event.reason,
stack: event.reason?.stack,
timestamp: new Date().toISOString()
};
sendErrorReport(errorReport);
event.preventDefault();
});
// 性能监控
function monitorPerformance() {
// 使用Performance API
if ('performance' in window) {
const timing = performance.timing;
const metrics = {
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
request: timing.responseStart - timing.requestStart,
response: timing.responseEnd - timing.responseStart,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
loadComplete: timing.loadEventEnd - timing.navigationStart,
total: timing.loadEventEnd - timing.navigationStart
};
// 发送性能数据
sendMetrics(metrics);
// 长任务监控
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.duration > 50) { // 超过50ms的任务
console.warn('长任务检测:', {
name: entry.name,
duration: entry.duration,
startTime: entry.startTime
});
}
});
});
observer.observe({ entryTypes: ['longtask'] });
}
}
}
// 内存泄漏检测
function checkMemoryLeaks() {
if ('memory' in performance) {
const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
const usagePercent = (usedJSHeapSize / jsHeapSizeLimit) * 100;
if (usagePercent > 80) {
console.warn('内存使用率过高:', `${usagePercent.toFixed(2)}%`);
// 触发垃圾回收(仅在Chrome中有效)
if (window.gc) {
window.gc();
}
}
}
}
// 定期检查
setInterval(checkMemoryLeaks, 60000); // 每分钟检查一次
五、实战案例:完整的错误处理系统
// 综合应用:一个完整的API请求模块
class ApiClient {
constructor(baseURL, config = {}) {
this.baseURL = baseURL;
this.config = {
timeout: 30000,
retries: 3,
logger: console,
...config
};
this.logger = this.config.logger;
this.requestInterceptor = null;
this.responseInterceptor = null;
}
// 请求拦截器
useRequestInterceptor(interceptor) {
this.requestInterceptor = interceptor;
}
// 响应拦截器
useResponseInterceptor(interceptor) {
this.responseInterceptor = interceptor;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const startTime = Date.now();
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2)}`;
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Request-ID': requestId,
...options.headers
},
timeout: this.config.timeout,
...options
};
// 请求拦截
if (this.requestInterceptor) {
const modifiedOptions = await this.requestInterceptor(requestOptions);
Object.assign(requestOptions, modifiedOptions);
}
this.logger.debug('API请求开始', {
requestId,
url,
method: requestOptions.method,
timeout: requestOptions.timeout
});
let attempts = 0;
let lastError;
while (attempts <= this.config.retries) {
attempts++;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), requestOptions.timeout);
requestOptions.signal = controller.signal;
const response = await fetch(url, requestOptions);
clearTimeout(timeoutId);
const duration = Date.now() - startTime;
// 响应拦截
let processedResponse = response;
if (this.responseInterceptor) {
processedResponse = await this.responseInterceptor(response.clone());
}
if (!response.ok) {
const error = new NetworkError(
`HTTP ${response.status}: ${response.statusText}`,
response.status
);
error.response = processedResponse;
error.requestId = requestId;
error.duration = duration;
// 特定状态码不重试
const noRetryStatuses = [400, 401, 403, 404];
if (noRetryStatuses.includes(response.status)) {
throw error;
}
lastError = error;
continue;
}
let data;
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
this.logger.info('API请求成功', {
requestId,
url,
method: requestOptions.method,
status: response.status,
duration,
dataSize: JSON.stringify(data).length
});
return {
success: true,
data,
response: processedResponse,
requestId,
duration
};
} catch (error) {
const duration = Date.now() - startTime;
lastError = error;
if (error.name === 'AbortError') {
this.logger.error('请求超时', {
requestId,
url,
method: requestOptions.method,
duration
});
} else {
this.logger.error('请求失败', {
requestId,
url,
method: requestOptions.method,
error: error.message,
duration,
attempt: attempts
});
}
if (attempts > this.config.retries) {
break;
}
// 指数退避延迟
const delay = Math.min(1000 * Math.pow(2, attempts - 1), 10000);
await this.sleep(delay);
}
}
throw lastError;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 快捷方法
get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// 使用示例
const apiClient = new ApiClient('https://api.example.com', {
timeout: 10000,
retries: 3
});
// 添加拦截器
apiClient.useRequestInterceptor(async (options) => {
const token = localStorage.getItem('auth_token');
if (token) {
options.headers.Authorization = `Bearer ${token}`;
}
return options;
});
apiClient.useResponseInterceptor(async (response) => {
if (response.status === 401) {
// 处理token过期
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
return response;
});
// 使用API客户端
async function getProducts() {
try {
const result = await apiClient.get('/products');
return result.data;
} catch (error) {
if (error instanceof NetworkError && error.statusCode === 404) {
console.warn('产品API端点不存在');
return [];
}
throw error;
}
}
六、最佳实践总结
6.1 错误处理原则
- 尽早捕获,明确处理:在可能出错的地方添加错误处理
- 提供有意义的错误信息:错误消息应帮助开发者快速定位问题
- 不要静默忽略错误:至少记录错误,即使你决定不处理它
- 区分预期错误和意外错误:网络错误是预期的,语法错误是意外的
- 保持错误处理的层次性:在合适的层级处理错误
6.2 调试策略
- 从简单开始:先检查控制台,使用console.log
- 利用断点:条件断点、日志点、DOM断点
- 性能监控:关注内存泄漏和长任务
- 生产环境监控:实现错误收集和性能追踪
- 编写可调试的代码:清晰的命名、适当的注释、模块化设计
结语
掌握JavaScript错误处理和调试技巧是提升开发效率和代码质量的关键。通过本文的学习,你应该能够:
- 识别和处理各种JavaScript错误
- 熟练使用浏览器调试工具
- 实现健壮的错误处理机制
- 构建完整的错误监控系统
- 遵循错误处理的最佳实践
记住,优秀的开发者不是不犯错误,而是能够快速定位和修复错误。良好的错误处理习惯会让你的代码更加可靠,你的调试技巧会让你在解决问题时事半功倍。