Chrome DevTools 高效调试指南:5 个替代 console.log 的实用技巧

技巧 1:条件断点 — 替代 if (id === 5) console.log(data)

场景

你有一个列表渲染了 100 条数据,但只有第 5 条数据有 bug。用 console.log 的话,要么打印 100 条慢慢找,要么加 if 判断:

// 你以前的做法
items.forEach(item => {
  if (item.id === 5) {
    console.log('有问题的数据:', item);
  }
  renderItem(item);
});

用条件断点

  1. 打开 DevTools → Sources 面板
  2. 找到对应代码,在行号上右键 → 选择 Add conditional breakpoint
  3. 输入条件:item.id === 5
  4. 回车确认

现在代码只会在 item.id === 5 时暂停,你可以在 Scope 面板里直接看到 item 的所有属性,不需要改任何代码。

进阶用法

条件表达式里可以写任何 JS,包括:

// 只在数组长度异常时断住
arr.length > 100

// 只在某个属性为 null 时断住
user.profile === null

// 组合条件
item.status === 'error' && item.retryCount > 3
提示:条件断点的颜色会变成橙色(普通断点是蓝色),方便你区分。

技巧 2:Logpoints — 不改代码就能打日志

场景

你想在某行代码执行时打印一些信息,但不想改代码(比如代码在 node_modules 里,或者你不想频繁保存触发热更新)。

操作步骤

  1. Sources 面板,在行号上右键 → 选择 Add logpoint
  2. 输入要打印的内容,语法和 console.log 一样:

    '用户数据:', user, '请求耗时:', Date.now() - startTime, 'ms'
  3. 回车确认,行号旁边会出现一个粉色菱形标记

代码执行到这一行时,会在 Console 面板输出你写的内容,但不会暂停执行。

console.log 的区别

对比项console.logLogpoint
需要改代码
需要保存刷新
调试完要删除关掉 DevTools 自动消失
node_modules 里能用不方便可以
这是我用得最多的功能。团队里有个同事之前遇到第三方库的 bug,加了 20 行 console.lognode_modules 里,调完了忘删,下一次 npm install 覆盖掉了他的调试代码,他又加了一遍。如果用 Logpoint,根本不需要碰源码。

技巧 3:Console 高级 API — 替代满屏的 console.log

大多数人只知道 console.log。但 Console API 还有这些:

console.table() — 表格展示数据

const users = [
  { id: 1, name: '张三', role: 'admin', active: true },
  { id: 2, name: '李四', role: 'user', active: false },
  { id: 3, name: '王五', role: 'user', active: true },
];

// 之前:console.log(users)  → 输出一坨折叠的对象

// 现在:
console.table(users);

输出是一个整齐的表格,列头是属性名,一目了然。还可以指定只显示某些列:

console.table(users, ['name', 'role']);

console.group() — 分组折叠

console.group('用户请求流程');
console.log('1. 发送请求');
console.log('2. 收到响应', response);
console.log('3. 更新状态');
console.groupEnd();

console.group('渲染流程');
console.log('1. 计算 diff');
console.log('2. 更新 DOM');
console.groupEnd();

输出会按组折叠,不再是一大坨混在一起的日志。用 console.groupCollapsed() 还能默认折叠。

console.time() — 精确计时

console.time('数据处理');
const result = processLargeDataset(rawData);
console.timeEnd('数据处理');
// 输出:数据处理: 142.38ms

Date.now() 相减更精确,而且不需要创建临时变量。

console.trace() — 打印调用栈

function updateUser(id, data) {
  console.trace('updateUser 被谁调用了');
  // ...
}

输出完整的调用链,直接告诉你这个函数是从哪个路径调进来的。排查"这个函数怎么被调了两次"的问题特别好用。

console.assert() — 条件日志

console.assert(user.age > 0, '年龄不应该小于等于0', user);

只有条件为 false 时才输出,替代 if (!xxx) console.log()


技巧 4:Network Override — 不改后端代码调试接口

场景

后端接口返回的数据有问题,你想临时改一下返回值来测试前端逻辑。以前你可能会:

  • 找后端同事帮忙改接口(等半天)
  • 在前端代码里硬编码 mock 数据(改完还得删)
  • 用 Charles/Whistle 抓包改响应(配置麻烦)

用 Network Override

  1. DevTools → Network 面板
  2. 找到目标请求,右键 → 选择 Override content
  3. DevTools 会让你选一个本地文件夹作为 override 目录
  4. 修改响应内容,保存

之后每次刷新页面,这个接口都会返回你修改后的内容,不需要改任何前端或后端代码。

实际案例

后端接口 /api/users 返回了 10 条数据,但你要测试"空列表"状态:

  1. 正常请求一次,Override content
  2. 把响应体改成 []
  3. 刷新页面,前端就会渲染空状态

要测试"超长数据"?把响应改成 1000 条数据。要测试"字段缺失"?删掉某个字段。

进阶:Override Headers

不只是响应体,你还可以 Override 响应头:

  • 模拟 CORS 错误:删掉 Access-Control-Allow-Origin
  • 模拟缓存策略:修改 Cache-Control
  • 模拟慢网络:在 Network 面板顶部的 Throttling 选项里选 Slow 3G

技巧 5:Live Expressions — 实时监控变量

场景

你想持续观察某个变量的值变化,用 console.log 的话,每次值变化都要重新打印,Console 面板很快就被刷屏了。

操作步骤

  1. DevTools → Console 面板
  2. 点击 Console 面板顶部的眼睛图标(Create live expression)
  3. 输入你要监控的表达式:

    document.querySelectorAll('.list-item').length

这个表达式的值会实时更新,不需要你手动执行。

常用监控表达式

// 监控页面上有多少 DOM 节点
document.querySelectorAll('*').length

// 监控某个全局状态
window.__STORE__.getState().user.loginStatus

// 监控页面滚动位置
Math.round(window.scrollY)

// 监控内存使用
(performance.memory.usedJSHeapSize / 1048576).toFixed(1) + 'MB'

你可以同时添加多个 Live Expression,相当于一个实时仪表盘,比反复 console.log 高效得多。


总结:场景对照表

调试场景console.log 做法DevTools 做法
查看某个变量值console.log(x)条件断点,在 Scope 面板看
在某行打印日志改代码加 logLogpoint,不改代码
打印数组/对象console.log(arr)console.table(arr)
分组打印手动加分隔符console.group()
计算耗时Date.now() 相减console.time()
查看调用栈console.trace()
修改接口返回值Mock 或找后端Network Override
实时监控变量setInterval + logLive Expression
调试 node_modules改源码(危险)Logpoint 或条件断点

一个实际 debug 故事

上个月我们项目有个 bug:列表页的分页组件在第 3 页之后会显示错误的总数。

如果用 console.log 调试,我得:

  1. 在分页组件里加 log,打印 total 值 → 值是对的
  2. 在请求函数里加 log,打印响应数据 → 数据也是对的
  3. 在状态管理里加 log,打印 store → 也是对的
  4. 困惑,加更多 log...

我实际的做法:

  1. Network Override 把第 3 页的接口响应 total 改成一个特殊值(比如 999)
  2. 页面显示 999 → 说明组件渲染逻辑没问题,数据能正确传到 UI
  3. 去掉 Override,用条件断点在状态更新的地方设 page === 3 的条件
  4. 断住在 Scope 面板发现:第 3 页请求回来时,有一个竞态条件——第 2 页的请求比第 3 页晚到达,把 total 覆盖成了第 2 页的值
从发现 bug 到定位根因:12 分钟。 如果用 console.log 大法,光加 log 删 log 就得半小时。

快捷键速查

操作MacWindows
打开 DevToolsCmd + Option + IF12
打开 Command MenuCmd + Shift + PCtrl + Shift + P
搜索文件Cmd + PCtrl + P
搜索代码Cmd + Option + FCtrl + Shift + F
切换面板Cmd + ] / Cmd + [Ctrl + ] / Ctrl + [
暂停/继续执行F8F8
单步执行F10F10
进入函数F11F11
提示:在 Command Menu(Cmd + Shift + P / Ctrl + Shift + P)里输入 logpointoverride 可以快速找到对应功能。

利用 WiFi 信号感知人体姿态的工具

https://github.com/ruvnet/RuView

在狼人杀里,和 AI 赛博斗蛐蛐

https://github.com/oil-oil/wolfcha

一个专注于白噪音播放的 Android 应用,帮助你放松、专注和入眠。

https://github.com/Tosencen/XMSLEEP

《从零开始学习英语语法》。这是一本面向英语基础薄弱同学的英语语法入门书籍,内容言简意赅、插图幽默风趣。

https://github.com/hzpt-inet-club/english-note

单文件即用的数据库管理工具

https://github.com/vrana/adminer?tab=readme-ov-file

Wifi信号感知周围区域CSI信号处理示例

https://github.com/espressif/esp-csi

neko浏览器,共享浏览器

https://neko.m1k1o.net/

配色相关分享

https://www.designprompts.dev/

sudo su

cd /etc/netplan

vi 00-installer-config.yaml

配置参考

network:
  ethernets:
    ens33:
      dhcp4: false
      addresses: [172.16.33.225/24]
      routes:
          - to: default
            via: 172.16.33.1
      nameservers:
        addresses: [114.114.114.114,8.8.8.8]
  version: 2

应用网络配置

netplan apply

创建项目目录

mkdir project_name

安装vite

npm init vite@latest project_name
framework选择Vue
variant选择JavaScript

进入项目目录

cd project_name

在当前目录创建环境配置文件

根据各自环境需求创建相应的环境配置文件
测试环境配置文件: .env.development
生产环境配置文件: .env.production

project_name/.env.development
project_name/.env.production

文件内容demo:

VITE_API_URL=http://127.0.0.1:9696      #api接口地址
VITE_SITE_PORT=9797                     #站点监听端口

配置vite.config.js,全局引入环境变量及配置@别名

project_name/vite.config.js:
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from "path"

export default ({ mode }) => {
  const env = loadEnv(mode, process.cwd());
  return defineConfig({
    define:{
      'process.env': env                        //注入配置文件环境变量
    },
    plugins: [vue()],
    resolve: {
      alias: {
        "@": resolve(__dirname, "./src")        //注入静态目录别名@
      }
    },
    server: {
      host: '0.0.0.0',
      port: Number(env.VITE_SITE_PORT),         //使用环境配置文件监听的端口
      open: true,                               //运行时自动打开网页
    }
  })
}

安装sass支持

npm install -D sass-loader sass

安装vue-router

npm install -S vue-router@next

新建路由配置文件index.js

project_name/src/router/index.js:
import { createRouter, createWebHashHistory } from "vue-router";

const routes = [
    {
        path: "/",
        name: "home",
        //记得顺便新建home文件的index.vue组件
        component: () => import("@/views/home/index.vue"),
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
});

export default router;

在src/main.js里面引用路由配置文件

project_name/src/main.js:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router"

const app = createApp(App);
app.use(router);
app.mount('#app');

在App.vue里面使用vue-router

<template>
  <router-view></router-view>
</template>

安装axios

npm install axios

在src下创建目录util及api.js文件

project_name/src/util/api.js:

请求接口及拦截处理需自行封装,以下是简单的demo:

import axios from 'axios';

const instance = axios.create({
  baseURL: process.env.VITE_API_URL || '/', // 设置基本 URL
  timeout: 120*1000 // 请求超时时间120秒
})

const _get = async(url, data) => {
    instance.defaults.headers.common['Authorization'] = "xxxx";
    return new Promise((resolve, reject) => {
      instance.get(url, data)
      .then(response => {
          resolve(resp.data);
      })
      .catch(error => reject(error));
    });
}

export default {
    _get:_get,
    // ...
}

添加自适应相关插件

amfe-flexble 根据当前页面的尺寸去设置根元素的font-size
npm i -S amfe-flexible
postcss-pxtorem 将px转换成rem
npm install postcss-pxtorem --save
在 main.js 中引入 amfe-flexble
import 'amfe-flexible'
vite.config.js中的配置
import postCssPxToRem from 'postcss-pxtorem'
export default defineConfig({
    plugins: [
        vue(),
    ],
    css: {
        postcss: {
            plugins: [
                postCssPxToRem({
                    // 自适应,px>rem转换
                    rootValue: 37.5,// 75表示750设计稿,37.5表示375设计稿
                    propList: ['*'],// 需要转换的属性,这里选择全部都进行转换
                    // selectorBlackList:['norem'],// 过滤掉norem-开头的class,不进行rem转换
                }),
            ],
        },
    }
});

面向对象(Object-Oriented,简称 OO)是一种编程思想和方法,它将程序中的数据和操作数据的方法封装在一起,形成"对象",并通过对象之间的交互和消息传递来完成程序的功能。面向对象编程强调数据的封装、继承、多态和动态绑定等特性,使得程序具有更好的可扩展性、可维护性和可重用性。

在面向对象的程序设计(英语:Object-oriented programming,缩写:OOP)中,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。

在现实世界里我们所面对的事情都是对象,如计算机、电视机、自行车等。

对象的主要三个特性:

对象的行为:对象可以执行的操作,比如:开灯,关灯就是行为。
对象的形态:对对象不同的行为是如何响应的,比如:颜色,尺寸,外型。
对象的表示:对象的表示就相当于身份证,具体区分在相同的行为与状态下有什么不同。
比如 Animal(动物) 是一个抽象类,我们可以具体到一只狗跟一只羊,而狗跟羊就是具体的对象,他们有颜色属性,可以写,可以跑等行为状态。

面向对象编程的三个主要特性:

封装(Encapsulation):指将对象的属性和方法封装在一起,使得外部无法直接访问和修改对象的内部状态。通过使用访问控制修饰符(public、private、protected)来限制属性和方法的访问权限,从而实现封装。

继承(Inheritance):指可以创建一个新的类,该类继承了父类的属性和方法,并且可以添加自己的属性和方法。通过继承,可以避免重复编写相似的代码,并且可以实现代码的重用。

多态(Polymorphism):指可以使用一个父类类型的变量来引用不同子类类型的对象,从而实现对不同对象的统一操作。多态可以使得代码更加灵活,具有更好的可扩展性和可维护性。在 PHP 中,多态可以通过实现接口(interface)和使用抽象类(abstract class)来实现。

面向对象内容
类 − 定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。

对象 − 是类的实例。

成员变量 − 定义在类内部的变量。该变量的值对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可成为对象的属性。

成员函数 − 定义在类的内部,可用于访问对象的数据。

继承 − 继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

父类 − 一个类被其他类继承,可将该类称为父类,或基类,或超类。

子类 − 一个类继承其他类称为子类,也可称为派生类。

多态 − 多态性是指相同的函数或方法可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。

重载 − 简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

抽象性 − 抽象性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。

封装 − 封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。

构造函数 − 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。

析构函数 − 析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做"清理善后" 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。