目录
很多 JavaScript 程序员都只会用console来调试代码,遇到一些问题需要写一大堆无用的console.log,调试效率非常低 。所谓工欲善其事必先利其器,本文教你完全掌握JavaScript调试技能。
Chrome DevTools 调试工具
Chrome Devtools 相信大家都不陌生,它可以说是前端开发者吃饭的工具。Chrome Devtools里的Source 面板就能调试当前网页的js。
在这里可以断点、单步调试等等,能详细地看到程序的运行状态。此外还有非常实用的 DOM断点、事件断点等功能,在定位特定问题的时候非常好用。其他功能建议去详细了解一下,本文只讲解JS断点调试的部分。
用Chrome打开一个网页,就可以直接在DevTools里面进行断点调试。
但是情况并没有这么简单。我们随便打开一个正式的网站,看到的代码通常是这样的:
浏览器中运行的 JS 代码通常情况下都是“编译”后的。这里的编译不是将源代码编译成了机器码(众所周知js是一种JIT语言,无须编译),而是打包工具对源代码进行了一些列转换、压缩、合并、混淆等操作,产出能被浏览器直接执行的js代码。源代码可以是 vue 、TS 、JSX 或者同样的是js。
我们可以用Chrome Devtools里的格式化功能先格式化一下代码,然后进行断点调试。但是简单格式化后的代码依旧很难看懂。
如何才能调试源码呢?答案是SourceMap。
什么是SourceMap
由于浏览器中运行的js代码通常都是经过了转换的,这给开发者调试带来了很大的困难,SourceMap就是为了解决这个问题而出现的。
SourceMap是一个信息文件,记录了转换后的代码与源代码之间的映射关系,浏览器可以通过这些映射关系还原源代码。
怎么启用SourceMap呢?
-
通过打包工具生成一个 source map文件
-
转换后的 JS 代码文件的末尾加上这样一行注释:
//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map
来看个实际例子。
有个简单的 TS 代码,test.ts :
const btn = document.getElementById('btn');
function add(a: number, b: number): number {
return a + b;
}
function handlerClick() {
console.log('1+2=', add(1, 2));
}
btn?.addEventListener('click', handlerClick);
我们用tsc编译代码时,可以选择生成souce map文件。tsconfig.json配置如下:
{
"compilerOptions": {
"outDir": "dist",
"sourceMap": true,
"preserveConstEnums": true,
"inlineSources": true
},
"include": ["src/**/*.ts"],
"exclude": ["dist/", "node_modules"]
}
执行 tsc ,得到编译产物有:test.js 和test.js.map 。
test.js 就是转换后的代码:
var btn = document.getElementById('btn');
function add(a, b) {
return a + b;
}
function handlerClick() {
console.log('1+2=', add(1, 2));
}
btn === null || btn === void 0 ? void 0 : btn.addEventListener('click', handlerClick);
//# sourceMappingURL=test1.js.map
test1.js.map 是SourceMap文件:
{
"version": 3,
"file": "test1.js",
"sourceRoot": "",
"sources": [
"../src/test1.ts"
],
"names": [],
"mappings": "AAAA,IAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAE3C,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAS,YAAY;IACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC",
"sourcesContent": [
"const btn = document.getElementById('btn');\n\nfunction add(a: number, b: number): number {\n return a + b;\n}\n\nfunction handlerClick() {\n console.log('1+2=', add(1, 2));\n}\n\nbtn?.addEventListener('click', handlerClick);\n\n\n"
]
}
我们来看看SourceMap中的内容:
-
version:使用的source map 标准 的版本
-
sources:源文件的名字
-
mappings:代码映射关系,使用的是Base 64 VLQ 格式
-
file:转换后的文件名
-
sourceContent:源代码的内容
其中mappings 的映射逻辑非常复杂,感兴趣可以阅读这篇文章: 《How do source maps work?》。
准备一个 HTML,用浏览器打开测试一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test测试页面</title>
</head>
<body>
<h1>测试一下</h1>
<button id="btn">点我</button>
<script src="./dist/test1.js"></script>
</body>
</html>
可以看到 Souces面板下的文件目录,会有一个 src/test1.ts 的文件。并且这个文件右下角声明了是“souce mapped from test1.js”。
可以直接在源码处断点调试。
不同的打包工具都有对应的sourcemap配置。值得注意的是,打包生产环境代码的时候不应该开启SourceMap,或者不应该把SourceMap文件放入服务器,避免泄漏源码。
在IDE中调试Web 应用
有了sourceMap,我们就可以在Chrome Devtools中调试源代码了。但这还不够方便,我们更希望直接在IDE中调试,这样既更方便阅读代码,同时也能边调试边修改。
Chrome DevTools工作原理
我们先了解一下Chrome DevTools的工作原理。Chrome DevTools可以分成Frontend、Backend和Protocol,以及传输信道。
Chrome DevTools Frontend 就是我们接触到的调试面板,它是一个Web应用。
Backend 可以是浏览器内核、Node.js等。
Chrome Devtools Protocol 是 Frontend 和 Backend 之间通信的协议,包含了DOM、网络、JS调试等调试能力,可以用于调试 Chrome、Chromium 和其他 Blink 内核的浏览器。
如果要使用IDE进行调试,就可以用IDE替代 Chrome Devtools Frontend的角色,解析对应的 Chrome Devtools Protocol 就行。
VS Code中调试
这里以VS Code为例,其他IDE功能类似。
首先必须用 debug 模式启动浏览器,这样才能进行调试。通过--remote-debugging-port 参数将浏览器的调试端口保留给外部程序使用。
VS Code Debugger可以分成两类,Launch和Attach。Launch是启动新的目标程序并进行调试;Attach是程序已经在运行时,“附着”上去进行调试。这两种方式没有本质的区别,只是区别在谁来启动程序而已。
用Launch方式调试的话,VS Code会自动以debug模式起一个新的Chrome进程;而用Attach方式的话,你就必须自己以debug模式启动。用 debug 模式启动 Chrome的方式为:
# Win
chrome.exe --remote-debugging-port=9222 --user-data-dir=remote-debug-profile
# MacOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=remote-debug-profile
两种调试方式的VS Code调试配置 launch.json 如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Launch Chrome",
"type": "pwa-chrome",
"request": "launch",
"url": "http://localhost:9080",
"webRoot": "${workspaceFolder}"
}
]
}
调试手机端Web 应用
前面讲了如何调试电脑端的web页面,那么手机端的Web页面改怎么调试呢?
用Chrome DevTools调试Android WebView

调试Android手机里的浏览器页面是比较简单的。
默认情况下Android 手机上的Chrome和原生浏览器都支持调试。如果想调试App内嵌的Webview,则需要先在App的代码中开启调试:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
调试Android WebView的步骤如下:
-
使用USB线链接电脑和手机;
-
手机开启USB调试模式;
-
电脑端Chrome打开地址:
chrome://inspect#devices; -
手机端允许USB调试并将USB链接方式选择为传输文件;
-
点击对应页面的inspect按钮,即可用Chrome DevTools对该页面进行调试;

DevTools 有个方便的端口转发功能,可以将手机上的请求转发到电脑上。例如我们在电脑端开启了http://localhost:9080 web服务,然后通过Port forwading 按钮设置如下:
然后在手机浏览器访问http://localhost:9080,然后在桌面端Chrome的inspect页面里打开调试。
用VS Code调试Android WebView
要使用VS Code来调试手机端的WebView,需要先将手机端WebView的调试协议端口转发到电脑端,然后再用Attach的方式进行调试。我们可以通过adb forward 命令来转发端口。
首先要安装好ADB 命令行程序。使用ADB调试手机WebView的原理为:
第一步,找到unix domain socket。通过USB链接手机后,执行命令:
adb shell cat /proc/net/unix | grep --text _devtools_remote
找到对应WebView的调试进程,例如chrome_devtools_remote或者huawei_webview_devtools_remote_31071。然后转发端口到电脑端的9222 端口(跟VS Code Attach端口一致):
adb forward tcp:9222 localabstract:chrome_devtools_remote
这个时候我们用电脑端打开地址 http://localhost:9222/json ,可以看到调试协议相关信息:
这个时候我们只需要在VS Code中Debugger面板里,选择Attach to Chrome即可进行调试。
到这里你肯定觉得这套操作下来太麻烦了。不用担心,已经有人封装好了一个VS Code插件可以直接使用。
在VS Code插件市场里搜索 Android WebView Debugging并安装,然后在launch.json中新增android-webview类型的调试,配置如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:9080",
"webRoot": "${workspaceFolder}"
},
{
"type": "android-webview",
"request": "attach",
"name": "Attach to Android WebView",
"application": "com.android.chrome",
"webRoot": "${workspaceFolder}"
}
]
}
此时在VS Code Debugger面板选择 android-webview 就能直接调试了。
调试iOS 上的Web
iOS上的浏览器——Safari使用的是 Blink内核,跟Chromium一样,所以他们的调试方式也很类似,只不过调试协议有一些差别。
最直接的调试方法就是使用MacOS上的Safari对iOS端的Safari进行调试。
先在桌面端Safari打开“开发”菜单。路径为:偏好设置-高级-在菜单栏中显示“开发”菜单。用电源线链接好设备和电脑,就可以在“开发”菜单中找到设备,并进行inspect。
那么能不能用Chrome DevTools或者VS Code来调试iOS WebView呢?答案是可以的,我们需要一个协议适配程序。
可以看下 remotedebug-ios-webkit-adapter 和 ios-webkit-debug-proxy。
调试 Node.js 应用
JavaScript除了运行在浏览器中,另一个重要的场景就是Node.js。接下来看看怎么调试Node.js程序。
一个简单的程序
先写个最简单的Node.js程序:
// test1.js
function sayHi(name) {
const str = 'Hello, world.';
console.log(name + ' says: ' + str);
}
sayHi('Bob');
测试运行一下:
> node test1.js
< Bob says: Hello, world.
用命令行调试
执行node inspect test1.js 可以进入命令行调试程序,会自动在运行的第一句代码处断点。
使用方式可以参考debugger文档。
node inspect 命令保留了9229调试端口,所以我们也可以使用Chrome DevTools和VS Code Attach方式进行调试。
用Chrome DevTools调试
也可以用Chrome打开chrome://inspect/ ,可以找到该进程进行调试。
用VS Code调试
VS Code Debugger 的Attach和Launch两种方式配置如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/test1.js"
}
]
}
Express.js + TS程序
写个简单的Hello-World程序。
.
├── package-lock.json
├── package.json
├── src
│ ├── app.ts
│ └── lib.ts
└── tsconfig.json
// src/app.ts
import * as express from 'express';
import { sayHI } from './lib';
const app = express()
const port = 3000
app.get('/', (req, res) => {
const result = sayHI('Bob');
res.send(result)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
// src/lib.ts
export function sayHI(name: string): string {
const str = 'Hello World!';
return name + ' says: ' + str;
}
先要用tsc 将TS编译成JS。npm install -D typescript ,然后执行 npx tsc。其中tsconfig.json配置如下:
{
"compilerOptions": {
"outDir": "dist",
"module": "CommonJS",
"preserveConstEnums": true,
"sourceMap": true
},
"include": ["src/**/*.ts"],
"exclude": ["dist/", "node_modules"]
}
编译后的代码为:
// dist/app.js
"use strict";
exports.__esModule = true;
var express = require("express");
var lib_1 = require("./lib");
var app = express();
var port = 3000;
app.get('/', function (req, res) {
var result = (0, lib_1.sayHI)('Bob');
res.send(result);
});
app.listen(port, function () {
console.log("Example app listening on port ".concat(port));
});
//# sourceMappingURL=app.js.map
用 inspect 方式启动node程序:
node --inspect dist/app.js
然后我们可以用Chrome DevTools、VS Code调试该程序。
用Chrome DevTools调试
Chrome页面 chrome://inspect/#devices 中可以看到正在运行的程序,然后点击inspect。打开后需要手动添加代码目录。点击左侧的“+ Add folder to workspace”,找到代码目录即可。
用VS Code调试
使用VS Code调试更加方便。如果Node.js 程序是以inspect模式启动的,那么直接 Attach 就行。也可以用VS Code 的launch模式启动程序并调试。launch.json配置如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Launch Program",
"program": "${workspaceFolder}/dist/app.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
}
]
}
用ts-node-dev进行调试
每次修改代码都需要重新编译和运行的话,对于开发非常不方便。使用ts-node-dev可以直接运行TS程序无须编译,并且具备热重载功能。
# 安装
npm install ts-node-dev --save-dev
# 运行
npx ts-node-dev --inspect -- src/app.ts
接下来就可以愉快地用Chrome DevTools或者VS Code进行调试了。