JavaScript疑难Bug?试试这8个专业调试技巧

一、使用console.trace()追踪调用栈

function functionA() {   functionB(); }

function functionB() {   functionC(); }

function functionC() {   console.trace('追踪调用路径');   // 这里有个错误但不知道从哪里调用的   throw new Error('示例错误'); }

functionA();

二、利用debugger语句和浏览器开发者工具

function calculateTotal(items) {
  let total = 0;
  debugger; // 执行会在这里暂停
  
  for (let i = 0; i <= items.length; i++) { // 故意错误:<= 应该是 <
    total += items[i].price;
  }
  
  return total;
}

const cart = [{price: 10}, {price: 20}, {price: 30}];
console.log(calculateTotal(cart));

三、使用try-catch捕获异步错误

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error(`HTTP错误! 状态码: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('获取用户数据失败:', error);
    // 可以在这里添加错误上报逻辑
    Sentry.captureException(error);
    throw error; // 重新抛出以便外部处理
  }
}

// 使用示例
fetchUserData(123)
  .then(data => console.log(data))
  .catch(err => console.log('处理错误:', err));

四、全局错误处理

window.onerror = function(message, source, lineno, colno, error) {
  console.error('全局捕获的异常:', {
    message,    // 错误信息
    source,     // 发生错误的脚本URL
    lineno,     // 行号
    colno,      // 列号
    error       // Error对象(包含调用栈)
  });
  
  // 可以在这里发送错误到监控系统
  sendErrorToServer({
    type: 'unhandled_error',
    details: { message, source, lineno, colno },
    stack: error?.stack
  });
  
  // 返回true可以阻止浏览器默认错误处理
  return true;
};

// 触发示例
undefinedFunction(); // 会触发window.onerror

五、Promise 未捕获异常处理

window.addEventListener('unhandledrejection', (event) => {
  const error = event.reason;
  const isExpectedError = error?.isExpected; // 自定义错误标识
  
  if (!isExpectedError) {
    sendToErrorMonitoring({
      type: 'unhandled_rejection',
      error: error?.toString(),
      stack: error?.stack,
      timestamp: new Date().toISOString()
    });
  }
  
  // 对重要错误显示用户通知
  if (error?.isCritical) {
    showErrorToast('操作失败,请稍后重试');
  }
});

// 触发示例
Promise.reject(new Error('异步操作失败'));
new Promise(() => { throw new Error('构造器中抛出异常') });

六、自定义错误类型

class AppError extends Error {
  constructor(message, statusCode = 500, details = {}) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.details = details;
    this.isOperational = true; // 标记为可预期的操作错误
    
    // 保留原始调用栈
    Error.captureStackTrace(this, this.constructor);
  }
}

// 使用示例
try {
  throw new AppError('用户未找到', 404, { userId: 123 });
} catch (error) {
  console.error(`${error.name}: ${error.message}`);
  console.error('状态码:', error.statusCode);
  console.error('详情:', error.details);
}

七、错误降级处理

async function fetchWithCacheFallback(url, cacheKey, options = {}) {
  try {
    const response = await fetch(url, options);
    if (!response.ok) throw new Error('Network response was not ok');
    
    const data = await response.json();
    // 更新缓存
    localStorage.setItem(cacheKey, JSON.stringify({
      data,
      timestamp: Date.now()
    }));
    return data;
  } catch (error) {
    console.warn('网络请求失败,尝试使用缓存:', error.message);
    
    // 尝试从缓存读取
    const cached = localStorage.getItem(cacheKey);
    if (cached) {
      const { data, timestamp } = JSON.parse(cached);
      if (Date.now() - timestamp < 24 * 60 * 60 * 1000) { // 1天内缓存有效
        return data;
      }
    }
    
    // 缓存不可用则抛出错误
    throw new Error('无法获取数据且无有效缓存');
  }
}

// 使用示例
try {
  const products = await fetchWithCacheFallback(
    '/api/products', 
    'products-cache'
  );
  displayProducts(products);
} catch (finalError) {
  showEmptyState('暂时无法显示产品列表');
}

八、异步错误处理链优化

1.日志等级标准定义

// 日志级别常量(符合RFC5424标准)
const LogLevel = {
  EMERGENCY: 0,   // 系统不可用
  ALERT: 1,       // 必须立即采取措施
  CRITICAL: 2,    // 严重情况
  ERROR: 3,       // 错误事件但系统仍能运行
  WARNING: 4,     // 警告事件
  NOTICE: 5,      // 正常但重要的事件
  INFO: 6,        // 常规信息
  DEBUG: 7        // 调试信息
};

// 反向映射用于输出
const LevelNames = Object.fromEntries(
  Object.entries(LogLevel).map(([k, v]) => [v, k])
);

2.日志记录器核心实现

class Logger {
  constructor(options = {}) {
    this.minLevel = options.minLevel || LogLevel.INFO;
    this.transports = options.transports || [new ConsoleTransport()];
    this.context = options.context || {};
    this.sensitiveFields = options.sensitiveFields || ['password', 'token'];
  }

  // 核心记录方法
  log(level, message, meta = {}) {
    if (level > this.minLevel) return;

    const entry = {
      timestamp: new Date().toISOString(),
      level,
      levelName: LevelNames[level],
      message,
      ...this._sanitize(meta),
      context: { ...this.context }
    };

    this.transports.forEach(transport => {
      try {
        transport.log(entry);
      } catch (transportError) {
        console.error('日志传输失败:', transportError);
      }
    });
  }

  // 数据脱敏处理
  _sanitize(data) {
    if (typeof data !== 'object' || data === null) return data;
    
    const sanitized = { ...data };
    this.sensitiveFields.forEach(field => {
      if (sanitized[field]) {
        sanitized[field] = '*****';
      }
    });
    return sanitized;
  }

  // 快捷方法
  emergency(message, meta) { this.log(LogLevel.EMERGENCY, message, meta); }
  alert(message, meta) { this.log(LogLevel.ALERT, message, meta); }
  critical(message, meta) { this.log(LogLevel.CRITICAL, message, meta); }
  error(message, meta) { this.log(LogLevel.ERROR, message, meta); }
  warning(message, meta) { this.log(LogLevel.WARNING, message, meta); }
  notice(message, meta) { this.log(LogLevel.NOTICE, message, meta); }
  info(message, meta) { this.log(LogLevel.INFO, message, meta); }
  debug(message, meta) { this.log(LogLevel.DEBUG, message, meta); }

  // 创建子记录器(继承上下文)
  child(context) {
    return new Logger({
      ...this,
      context: { ...this.context, ...context }
    });
  }
}

3.日志控制台传输

class ConsoleTransport {
  constructor(options = {}) {
    this.format = options.format || 'text'; // 'text' | 'json'
    this.colors = {
      [LogLevel.EMERGENCY]: '\x1b[41m',   // 红底
      [LogLevel.ALERT]: '\x1b[35m',       // 紫色
      [LogLevel.CRITICAL]: '\x1b[31m',    // 红色
      [LogLevel.ERROR]: '\x1b[91m',       // 亮红
      [LogLevel.WARNING]: '\x1b[33m',     // 黄色
      [LogLevel.NOTICE]: '\x1b[36m',      // 青色
      [LogLevel.INFO]: '\x1b[32m',        // 绿色
      [LogLevel.DEBUG]: '\x1b[90m',       // 灰色
      reset: '\x1b[0m'
    };
  }

  log(entry) {
    if (this.format === 'json') {
      console.log(JSON.stringify(entry));
      return;
    }

    const color = this.colors[entry.level] || '';
    const levelName = entry.levelName.padEnd(8);
    const prefix = `${color}[${entry.timestamp}] ${levelName}:${this.colors.reset}`;
    
    console.log(`${prefix} ${entry.message}`);
    if (Object.keys(entry.context).length > 0) {
      console.log('  Context:', entry.context);
    }
    if (Object.keys(entry).filter(k => !['timestamp','level','levelName','message','context'].includes(k)).length > 0) {
      console.log('  Metadata:', 
        Object.fromEntries(
          Object.entries(entry)
            .filter(([k]) => !['timestamp','level','levelName','message','context'].includes(k))
      );
    }
  }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

局外人LZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值