Skip to content

Console 调试技巧:浏览器开发者的瑞士军刀

不只是 console.log

每个 JavaScript 开发者的调试之旅都是从 console.log 开始的。但 Console API 远不止于此——它是一个功能丰富的调试工具箱,包含了日志分级、格式化输出、性能测量、调用追踪等强大功能。

熟练掌握这些技巧,就像给你的调试过程装上了涡轮增压,能够更快地定位问题、更清晰地展示数据、更高效地分析性能。

基础日志方法

console.log() - 通用日志

javascript
// 最基础的用法
console.log("Hello, World!");

// 输出多个值
const name = "Sarah";
const age = 28;
console.log("用户信息:", name, age);

// 使用模板字符串
console.log(`用户 ${name} 的年龄是 ${age}`);

// 输出对象
const user = { name: "Michael", email: "[email protected]" };
console.log("用户对象:", user);

// 输出数组
const numbers = [1, 2, 3, 4, 5];
console.log("数组:", numbers);

console.info() - 信息日志

javascript
// 语义上表示信息性消息
console.info("应用程序已启动");
console.info("当前版本: 2.1.0");
console.info("API 端点:", "https://api.example.com");

// 在某些浏览器中,info 会显示一个蓝色的信息图标

console.warn() - 警告日志

javascript
// 输出警告信息,通常显示为黄色
console.warn("此功能即将弃用");
console.warn("配置项缺失,使用默认值");

// 带有详细信息的警告
function deprecatedFunction() {
  console.warn(
    "⚠️ deprecatedFunction() 已弃用,请使用 newFunction() 替代。" +
      "\n将在 v3.0 版本中移除。"
  );
  // 函数逻辑...
}

console.error() - 错误日志

javascript
// 输出错误信息,通常显示为红色
console.error("无法连接到服务器");
console.error("用户认证失败");

// 输出错误对象
try {
  throw new Error("Something went wrong");
} catch (error) {
  console.error("捕获到错误:", error);
  console.error("错误堆栈:", error.stack);
}

// 自定义错误信息
function fetchData(url) {
  if (!url) {
    console.error("fetchData 错误: URL 参数不能为空");
    return null;
  }
  // ...
}

console.debug() - 调试日志

javascript
// 用于调试信息,在某些浏览器中默认不显示
// 需要在开发者工具中启用 "Verbose" 级别
console.debug("调试信息: 函数开始执行");
console.debug("变量状态:", { x: 10, y: 20 });

// 调试模式下的详细输出
const DEBUG_MODE = true;

function debugLog(...args) {
  if (DEBUG_MODE) {
    console.debug("[DEBUG]", new Date().toISOString(), ...args);
  }
}

debugLog("请求开始", { url: "/api/users" });

格式化输出

字符串格式化

Console API 支持类似 C 语言 printf 的格式化语法:

javascript
// %s - 字符串
console.log("Hello, %s!", "World");
// 输出: Hello, World!

// %d 或 %i - 整数
console.log("订单数量: %d", 42);
// 输出: 订单数量: 42

// %f - 浮点数
console.log("价格: %f 元", 99.99);
// 输出: 价格: 99.99 元

// %o - 可展开的对象
const user = { name: "John", role: "admin" };
console.log("用户: %o", user);

// %O - 对象的完整表示(与 %o 类似,但显示更多属性)
console.log("DOM 元素: %O", document.body);

// %c - CSS 样式(下一节详细介绍)
console.log("%c样式化文本", "color: blue;");

// 组合使用
console.log("用户 %s (ID: %d) 消费了 %f 元", "Sarah", 12345, 299.5);

CSS 样式化输出

使用 %c 可以为日志添加 CSS 样式,让输出更加醒目:

javascript
// 基础样式
console.log("%c红色文字", "color: red;");
console.log("%c大号蓝色文字", "color: blue; font-size: 20px;");

// 背景色和边框
console.log(
  "%c重要通知",
  "background: #ff6b6b; color: white; padding: 4px 8px; border-radius: 4px;"
);

// 多段不同样式
console.log(
  "%c成功 %c警告 %c错误",
  "color: green; font-weight: bold;",
  "color: orange; font-weight: bold;",
  "color: red; font-weight: bold;"
);

// 实用的日志工具
const logStyles = {
  success:
    "background: #4CAF50; color: white; padding: 2px 6px; border-radius: 3px;",
  warning:
    "background: #ff9800; color: white; padding: 2px 6px; border-radius: 3px;",
  error:
    "background: #f44336; color: white; padding: 2px 6px; border-radius: 3px;",
  info: "background: #2196F3; color: white; padding: 2px 6px; border-radius: 3px;",
};

function styledLog(type, message) {
  console.log(`%c${type.toUpperCase()}`, logStyles[type], message);
}

styledLog("success", "用户登录成功");
styledLog("warning", "会话即将过期");
styledLog("error", "网络请求失败");
styledLog("info", "正在加载数据...");

// 品牌化日志
console.log(
  "%cMyApp %cv2.0.0",
  "color: #764abc; font-size: 24px; font-weight: bold;",
  "color: #999; font-size: 12px;"
);

结构化数据展示

console.table() - 表格输出

console.table() 是展示数组和对象数据的神器:

javascript
// 数组展示
const users = [
  { id: 1, name: "Sarah", role: "Admin" },
  { id: 2, name: "Michael", role: "Editor" },
  { id: 3, name: "Emma", role: "Viewer" },
];

console.table(users);
// 输出漂亮的表格,包含 id、name、role 列

// 只显示特定列
console.table(users, ["name", "role"]);

// 对象展示
const serverConfig = {
  host: "localhost",
  port: 3000,
  database: "myapp_db",
  cache: true,
};

console.table(serverConfig);

// 嵌套数组
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

console.table(matrix);

console.dir() - 对象详情

javascript
// 以树形结构展示对象
const element = document.querySelector("body");
console.dir(element);
// 显示 DOM 元素的所有属性和方法

// 对比 log 和 dir
console.log(document.body); // 显示 HTML 结构
console.dir(document.body); // 显示对象属性

// 探索复杂对象
console.dir(window.navigator, { depth: 2 });

console.dirxml() - XML/DOM 展示

javascript
// 以 XML/HTML 格式展示 DOM 元素
console.dirxml(document.body);

// 创建并展示元素
const div = document.createElement("div");
div.innerHTML = "<span>Hello</span><span>World</span>";
console.dirxml(div);

分组和折叠

console.group() - 日志分组

javascript
// 创建分组
console.group("用户信息");
console.log("名称: Sarah");
console.log("邮箱: [email protected]");
console.log("角色: Admin");
console.groupEnd();

// 嵌套分组
console.group("API 请求");
console.log("URL: /api/users");
console.group("请求头");
console.log("Content-Type: application/json");
console.log("Authorization: Bearer xxx");
console.groupEnd();
console.group("响应");
console.log("状态: 200");
console.log("数据: [...]");
console.groupEnd();
console.groupEnd();

// 实际应用:函数执行追踪
function processOrder(orderId) {
  console.group(`处理订单 #${orderId}`);

  console.log("1. 验证订单...");
  // 验证逻辑

  console.log("2. 计算价格...");
  // 价格计算

  console.log("3. 生成发票...");
  // 发票生成

  console.log("✓ 订单处理完成");
  console.groupEnd();
}

processOrder(12345);

console.groupCollapsed() - 折叠分组

javascript
// 默认折叠的分组
console.groupCollapsed("详细日志(点击展开)");
console.log("日志条目 1");
console.log("日志条目 2");
console.log("日志条目 3");
console.groupEnd();

// 实用模式:调试信息默认折叠
function debugInfo(label, data) {
  console.groupCollapsed(`[DEBUG] ${label}`);
  console.log("时间:", new Date().toISOString());
  console.log("数据:", data);
  console.trace("调用堆栈");
  console.groupEnd();
}

debugInfo("用户操作", { action: "click", target: "submit-button" });

性能测量

console.time() - 计时器

javascript
// 基础计时
console.time("数据加载");
// 模拟耗时操作
for (let i = 0; i < 1000000; i++) {}
console.timeEnd("数据加载");
// 输出: 数据加载: 5.123ms

// 多个计时器
console.time("总时间");
console.time("步骤1");
// 步骤1操作
console.timeEnd("步骤1");

console.time("步骤2");
// 步骤2操作
console.timeEnd("步骤2");
console.timeEnd("总时间");

// 实际应用:API 请求计时
async function fetchUserData(userId) {
  console.time(`获取用户 ${userId}`);

  try {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    console.timeEnd(`获取用户 ${userId}`);
    return data;
  } catch (error) {
    console.timeEnd(`获取用户 ${userId}`);
    console.error("请求失败:", error);
    throw error;
  }
}

console.timeLog() - 中间计时

javascript
// 在计时过程中输出中间时间
console.time("多步骤操作");

// 步骤1
await doStep1();
console.timeLog("多步骤操作", "- 步骤1完成");

// 步骤2
await doStep2();
console.timeLog("多步骤操作", "- 步骤2完成");

// 步骤3
await doStep3();
console.timeEnd("多步骤操作");

// 输出:
// 多步骤操作: 120ms - 步骤1完成
// 多步骤操作: 350ms - 步骤2完成
// 多步骤操作: 500ms

console.count() - 计数器

javascript
// 统计执行次数
function handleClick() {
  console.count("点击次数");
  // 点击处理逻辑
}

// 多次调用后:
// 点击次数: 1
// 点击次数: 2
// 点击次数: 3

// 带标签的计数
function trackEvent(eventType) {
  console.count(eventType);
}

trackEvent("pageview"); // pageview: 1
trackEvent("click"); // click: 1
trackEvent("pageview"); // pageview: 2
trackEvent("click"); // click: 2

// 重置计数
console.countReset("click");
trackEvent("click"); // click: 1

条件输出和断言

console.assert() - 断言

javascript
// 只有条件为 false 时才输出
const age = 15;
console.assert(age >= 18, "用户未满18岁");
// 输出: Assertion failed: 用户未满18岁

// 条件为 true 时不输出任何内容
console.assert(age > 0, "年龄必须为正数");
// 无输出

// 实际应用:参数验证
function divide(a, b) {
  console.assert(b !== 0, "除数不能为零", { a, b });
  return a / b;
}

divide(10, 0);
// 输出: Assertion failed: 除数不能为零 {a: 10, b: 0}

// 检查 DOM 元素
function initializeWidget(container) {
  console.assert(
    container instanceof HTMLElement,
    "container 必须是有效的 DOM 元素",
    container
  );
  // 初始化逻辑...
}

堆栈追踪

console.trace() - 堆栈跟踪

javascript
// 打印调用堆栈
function level3() {
  console.trace("堆栈跟踪");
}

function level2() {
  level3();
}

function level1() {
  level2();
}

level1();
// 输出完整的调用堆栈:
// level3
// level2
// level1
// (anonymous)

// 调试事件处理器的来源
document.addEventListener("click", function handleClick(event) {
  console.trace("点击事件触发");
});

// 追踪函数调用
function trackCall(fn) {
  return function (...args) {
    console.trace(`调用 ${fn.name}`);
    return fn.apply(this, args);
  };
}

const trackedFetch = trackCall(fetch);

清理控制台

console.clear()

javascript
// 清除控制台所有输出
console.clear();

// 实际应用:页面加载时清理
window.addEventListener("load", () => {
  console.clear();
  console.log("应用已启动");
});

// 开发模式下的自动清理
if (process.env.NODE_ENV === "development") {
  console.clear();
}

实用调试技巧

条件断点日志

javascript
// 在开发者工具中设置条件断点时使用
// 不会中断执行,但会输出日志
// 断点条件: console.log('变量值:', x) || false

// 更优雅的方式:使用逗号运算符
// 断点条件: (console.log('状态:', state), false)

高亮特定日志

javascript
// 创建醒目的分隔线
console.log("%c" + "=".repeat(50), "color: #ff6b6b;");
console.log(
  "%c>>> 重要调试点 <<<",
  "color: red; font-size: 16px; font-weight: bold;"
);
console.log("%c" + "=".repeat(50), "color: #ff6b6b;");

// 带图标的日志
const icons = {
  success: "✅",
  error: "❌",
  warning: "⚠️",
  info: "ℹ️",
  debug: "🐛",
  rocket: "🚀",
};

function logWithIcon(icon, message, ...args) {
  console.log(`${icons[icon] || icon} ${message}`, ...args);
}

logWithIcon("success", "操作完成");
logWithIcon("rocket", "应用启动");
logWithIcon("🎉", "任务完成!");

对象快照

javascript
// 避免对象引用问题
const state = { count: 0 };

console.log("初始状态:", state);
state.count = 10;
console.log("修改后:", state);
// 两次输出可能显示相同的值!

// 解决方案:创建快照
console.log("初始状态:", JSON.parse(JSON.stringify(state)));
state.count = 10;
console.log("修改后:", JSON.parse(JSON.stringify(state)));

// 或使用展开运算符(浅拷贝)
console.log("状态:", { ...state });

增强版日志类

javascript
class Logger {
  static levels = {
    DEBUG: 0,
    INFO: 1,
    WARN: 2,
    ERROR: 3,
    NONE: 4,
  };

  static currentLevel = this.levels.DEBUG;
  static showTimestamp = true;

  static setLevel(level) {
    this.currentLevel = this.levels[level] ?? level;
  }

  static getTimestamp() {
    return this.showTimestamp ? `[${new Date().toISOString()}]` : "";
  }

  static debug(...args) {
    if (this.currentLevel <= this.levels.DEBUG) {
      console.debug(`${this.getTimestamp()} [DEBUG]`, ...args);
    }
  }

  static info(...args) {
    if (this.currentLevel <= this.levels.INFO) {
      console.info(
        `%c${this.getTimestamp()} [INFO]`,
        "color: #2196F3;",
        ...args
      );
    }
  }

  static warn(...args) {
    if (this.currentLevel <= this.levels.WARN) {
      console.warn(`${this.getTimestamp()} [WARN]`, ...args);
    }
  }

  static error(...args) {
    if (this.currentLevel <= this.levels.ERROR) {
      console.error(`${this.getTimestamp()} [ERROR]`, ...args);
    }
  }

  static group(label, fn) {
    console.group(label);
    try {
      fn();
    } finally {
      console.groupEnd();
    }
  }

  static time(label, fn) {
    console.time(label);
    try {
      return fn();
    } finally {
      console.timeEnd(label);
    }
  }

  static async timeAsync(label, fn) {
    console.time(label);
    try {
      return await fn();
    } finally {
      console.timeEnd(label);
    }
  }

  static table(data, columns) {
    console.table(data, columns);
  }
}

// 使用示例
Logger.setLevel("DEBUG");

Logger.debug("调试信息");
Logger.info("用户已登录", { userId: 123 });
Logger.warn("API 响应缓慢");
Logger.error("连接失败");

Logger.group("用户操作", () => {
  Logger.info("点击按钮");
  Logger.info("提交表单");
});

const result = Logger.time("计算", () => {
  // 耗时计算
  return 42;
});

生产环境处理

在生产环境中,通常需要禁用或限制 console 输出:

javascript
// 方法1:完全禁用
if (process.env.NODE_ENV === "production") {
  console.log = () => {};
  console.debug = () => {};
  console.info = () => {};
  console.warn = () => {};
  // 保留 console.error 以便追踪问题
}

// 方法2:使用包装器
const isDev = process.env.NODE_ENV !== "production";

const logger = {
  log: isDev ? console.log.bind(console) : () => {},
  debug: isDev ? console.debug.bind(console) : () => {},
  info: isDev ? console.info.bind(console) : () => {},
  warn: isDev ? console.warn.bind(console) : () => {},
  error: console.error.bind(console), // 始终保留

  // 可选:发送错误到日志服务
  report(error, context = {}) {
    console.error(error);
    if (!isDev) {
      // 发送到错误追踪服务
      // sendToErrorService({ error, context });
    }
  },
};

// 方法3:构建时移除
// 使用 babel-plugin-transform-remove-console
// 或 terser 的 drop_console 选项

Webpack / Vite 配置

javascript
// vite.config.js
export default {
  build: {
    minify: "terser",
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
};

// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
    ],
  },
};

Console API 速查表

方法用途示例
log()通用日志console.log('message')
info()信息日志console.info('info')
warn()警告日志console.warn('warning')
error()错误日志console.error('error')
debug()调试日志console.debug('debug')
table()表格展示console.table(array)
dir()对象详情console.dir(object)
group()分组开始console.group('label')
groupEnd()分组结束console.groupEnd()
time()开始计时console.time('label')
timeEnd()结束计时console.timeEnd('label')
timeLog()中间计时console.timeLog('label')
count()计数console.count('label')
assert()断言console.assert(cond, msg)
trace()堆栈追踪console.trace('label')
clear()清除控制台console.clear()

总结

Console API 是每个 JavaScript 开发者的必备工具。从简单的 console.log 到复杂的分组、计时和样式化输出,这些工具能够帮助你更高效地调试代码。

关键要点:

  • 使用 warn()error() 区分日志级别
  • table() 是展示结构化数据的最佳选择
  • time() / timeEnd() 用于性能测量
  • group() 组织相关日志,保持控制台整洁
  • trace() 帮助追踪函数调用来源
  • 生产环境要注意移除或禁用调试日志

掌握这些技巧,让你的调试过程更加顺畅、高效。