JavaScript 斷點(diǎn)調(diào)試技巧
為什么要使用 debugger
這篇文章將介紹如何使用斷點(diǎn)來(lái)進(jìn)行 JavaScript 調(diào)試。在讀這篇文章之前,需要問(wèn)一個(gè)問(wèn)題:為什么要使用斷點(diǎn)來(lái)進(jìn)行調(diào)試?
我們首先需要認(rèn)可使用斷點(diǎn)的是必要的,否則下文介紹的所有斷點(diǎn)調(diào)試方法都會(huì)是廢話。console.log 是前端開發(fā)最常用的調(diào)試手段,它簡(jiǎn)單直接解決一部分問(wèn)題。但當(dāng)遇到十分復(fù)雜的問(wèn)題,console.log 就會(huì)變得不趁手。比如:
- 一個(gè)邏輯復(fù)雜的算法
如果你刷過(guò) leetcode 一定深有體會(huì),算法某個(gè)測(cè)試用例報(bào)錯(cuò)了,有時(shí)很難光靠目測(cè)找出有問(wèn)題的那個(gè)方法。
- 一個(gè)復(fù)現(xiàn)步驟十分繁瑣的bug。
花了10分鐘好不容易復(fù)現(xiàn)了,但是只跟蹤到某行代碼,需要第二次添加 log 才能繼續(xù)尋找問(wèn)題。查看log -> 添加log -> 查看log... 這個(gè)過(guò)程重復(fù)幾遍,今天剩下的磚就搬不完了。
- 一段運(yùn)行流程冗長(zhǎng)的代碼
- 一段沒(méi)有注釋、起名隨意的代碼
- server 端代碼
有 nodejs 服務(wù)端開發(fā)經(jīng)驗(yàn)的同學(xué)相信有過(guò)在 postman 和 ide 之間反復(fù)橫跳的經(jīng)歷,如果光靠 log,對(duì)于一個(gè)巨大的復(fù)雜對(duì)象,控制臺(tái)是不好查看全貌的。如果一個(gè)接口還涉及到數(shù)據(jù)庫(kù)增刪、第三方依賴,那么復(fù)原上一次請(qǐng)求造成的后果也是一件痛苦的事情。
在這些情況下,斷點(diǎn)調(diào)試是非常有價(jià)值的,將 debug 的時(shí)間復(fù)雜度從 O(n) 降到 O(1),讓搬磚更快樂(lè)。
這是文章的內(nèi)容大綱:
- Chrome debugger 基本用法
- VS Code 調(diào)試 SPA 應(yīng)用
- Chrome 調(diào)試 Nodejs
- VS Code 調(diào)試 Nodejs
Chrome debugger 基本用法
最簡(jiǎn)單的斷點(diǎn)調(diào)試,就是在代碼中加一句 debugger,然后到瀏覽器中刷新頁(yè)面,這時(shí)候?yàn)g覽器就會(huì)在 debugger 語(yǔ)句那停止執(zhí)行。
為了方便理解,引入一個(gè)簡(jiǎn)單例子,在一個(gè)文件夾中創(chuàng)建 index.html 和 index.js,然后在 index.html 中引入 index.js。index.js 內(nèi)容如下:
- // 國(guó)際慣例,hello world。
- const greet = () => {
- const greeting = "hello debugger";
- // 瀏覽器執(zhí)行到這里將會(huì)暫停
- debugger
- console.log(greeting);
- };
- greet();
- console.log("js evaluation done");
執(zhí)行命令:
- npm i -g serve
- serve .
然后訪問(wèn) http://localhost:5000并打開開發(fā)者工具。
這時(shí)候我們的 hello world 斷點(diǎn)就打上了,就像這樣:
d8090eb4-4a1f-4dfb-bf30-c08c762d66ad
圖中分為四個(gè)區(qū)域,藍(lán)色區(qū)域用于文件選擇,Page 一欄是指當(dāng)前頁(yè)面中的 JS 文件,F(xiàn)ilesystem 會(huì)顯示我們系統(tǒng)中的文件。通常我們使用 Page。
粉色是代碼的行號(hào)和內(nèi)容。代碼的行號(hào)處可以通過(guò)點(diǎn)擊來(lái)添加新的斷點(diǎn),再次點(diǎn)擊后取消。
黃色區(qū)域用于控制代碼的執(zhí)行,只需要掌握前四個(gè)按鈕的含義,就可以應(yīng)付絕大多數(shù)場(chǎng)景。按鈕1是讓代碼繼續(xù)執(zhí)行(resume),如果遇到下一個(gè)斷點(diǎn)就會(huì)再次中斷執(zhí)行。按鈕2可以讓瀏覽器執(zhí)行當(dāng)前行(圖中是第3行),然后在下一行中斷代碼,按鈕3是進(jìn)入當(dāng)前函數(shù),查看函數(shù)具體內(nèi)容。假設(shè)我們當(dāng)前停在第7行 greet() ,點(diǎn)擊按鈕3就會(huì)進(jìn)入 greet 方法中(也就是第2行)。如果不想再看 greet 方法了,就點(diǎn)擊按鈕4,跳出這個(gè)方法,回到第8行。
綠色區(qū)域可以查看變量的內(nèi)容和當(dāng)前的調(diào)用棧。
debugger 是最簡(jiǎn)單粗暴的打斷點(diǎn)方式,但是需要修改我們的代碼。需要注意的是,上線前必須刪除這些語(yǔ)句。也可以通過(guò)配置 webpack 來(lái)自動(dòng)去除。不過(guò)終究還是有些不方便,所以我們來(lái)看下如何通過(guò) vscode 來(lái)簡(jiǎn)化打斷點(diǎn)的方式。
VS Code 調(diào)試 SPA 應(yīng)用
首先我們使用 Vite 來(lái)創(chuàng)建一個(gè) Vue 應(yīng)用用于演示(React步驟類似)。
- # 創(chuàng)建 vut-ts 應(yīng)用
- npm init vite
- cd hello-vite
- npm install
- # 調(diào)用 VS Code cli 打開項(xiàng)目,
- # 或者手動(dòng)在 VS Code 打開。
- code .
- npm run dev
然后在 VS Code 中新建一個(gè)文件 .vscode/launch.json,填入這些內(nèi)容:
- {
- "version": "0.2.0",
- "configurations": [
- {
- "type": "pwa-chrome",
- "request": "launch",
- "name": "Launch Vue project",
- // 這里填入項(xiàng)目的訪問(wèn)地址
- "url": "http://localhost:3000",
- "webRoot": "${workspaceFolder}"
- },
- ]
- }
然后使用 cmd+q 退出你正在運(yùn)行的 Chrome(這步很重要,不能跳過(guò)),按 f5 啟動(dòng) VS Code 的調(diào)試功能。VS Code 就會(huì)幫你啟動(dòng)一個(gè) Chrome 窗口,并訪問(wèn)上述配置的中的 url。這時(shí)候我們的斷點(diǎn)就生效了,可以一步一步地控制代碼的運(yùn)行,找出 bug 來(lái)源。
這里有一個(gè)實(shí)用的小技巧,就是在 BREAKPOINTS 中,把 Uncaught Exceptions 勾上,這樣在代碼報(bào)錯(cuò)的地方,就會(huì)自動(dòng)中斷執(zhí)行。當(dāng)我們遇到一個(gè)報(bào)錯(cuò)時(shí),采用這個(gè)方法可以省去定位問(wèn)題代碼的時(shí)間。
另外我們可以發(fā)現(xiàn),在 VS Code 斷點(diǎn)生效時(shí),Chrome Devtools 也會(huì)同步這個(gè)展示這個(gè)斷點(diǎn)。
在 VS Code 中,調(diào)試有兩種模式,分別是 launch 和 attach。由于真正執(zhí)行代碼的是 Chrome 中的 JS 引擎,所以是否中斷代碼的控制權(quán)是在 Chrome 手里的。那為什么 VS Code 的斷點(diǎn)可以控制代碼的中斷呢?是因?yàn)?VS Code 通過(guò) devtools-protocol 向 Chrome 發(fā)起指令,告訴 Chrome 需要在哪一行代碼暫停執(zhí)行。這個(gè)發(fā)送指令的過(guò)程,被稱作 attach。而 launch 的過(guò)程包含 attach ,即先 launch(啟動(dòng)) 瀏覽器,然后 attach(附加) 斷點(diǎn)信息。所以 attach 模式是 launch 模式的子集。
聽起來(lái)好像 launch 模式會(huì)更方便,為我們省去了手動(dòng)啟動(dòng)瀏覽器的過(guò)程。但是這存在一個(gè)問(wèn)題,如果同時(shí)開發(fā)多個(gè)前端工程會(huì)怎樣?每個(gè)工程啟動(dòng)一個(gè)調(diào)試進(jìn)程,就會(huì)打開多個(gè)瀏覽器,那么在多個(gè)瀏覽器之間切換就會(huì)顯得很麻煩。我們可以使用 attach 模式解決這個(gè)問(wèn)題。
首先我們使用命令行啟動(dòng) Chrome。使用命令行的原因是,我們需要給 Chrome 的啟動(dòng)傳參。
- # 運(yùn)行這條命令前需要cmd+q退出已運(yùn)行的Chrome
- /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
- # 如果看到這個(gè)輸出,說(shuō)明傳參成功。
- DevTools listening on ws://127.0.0.1:9222/devtools/browser/856a3533-ca5c-474f-a0cf-88b7ae94c75b
VS Code 和 Chrome 是通過(guò) websocket 交流,--remote-debugging-port 指定了 websocket 使用的端口。然后我們將 launch.json 文件修改成這樣:
- {
- "version": "0.2.0",
- "configurations": [
- {
- "type": "pwa-chrome",
- "request": "attach",
- "name": "Vue Application",
- // 項(xiàng)目訪問(wèn)的 url
- "url": "http://localhost:3000",
- // websocket 端口,需要與 --remote-debugging-port 參數(shù)保持一致。
- "port": 9222,
- "webRoot": "${workspaceFolder}"
- },
- ]
- }
注意在啟動(dòng) VS Code 調(diào)試之前,需要在 Chrome 中打開 http://localhost:3000 這個(gè)頁(yè)面。然后我們?cè)?VS Code 中打上斷點(diǎn),刷新瀏覽器,代碼就成功停在斷點(diǎn)處了。第二個(gè)、第n個(gè)工程都可以采用相同的配置,區(qū)別是 url 字段要根據(jù)項(xiàng)目配置進(jìn)行修改。
Chrome 調(diào)試 Nodejs
上文講的是如何調(diào)試頁(yè)面,接下來(lái)我們聊如何調(diào)試 nodejs 應(yīng)用。首先來(lái)一個(gè)最容易上手的例子,創(chuàng)建一個(gè) hello world:
- // debug.js 文件
- const greeting = 'hello nodejs debugger'
- debugger
- console.log(greeting)
然后運(yùn)行這個(gè)文件
- node --inspect-brk debug.js
- Debugger listening on ws://127.0.0.1:9229/b9a6d6bf-baaa-4ad5-8cc6-01eb69e99f0a
- For help, see: https://nodejs.org/en/docs/inspector
--inspect-brk 表示運(yùn)行這個(gè) js 文件的同時(shí),在文件的第一行打上斷點(diǎn)。然后打開 Chrome,進(jìn)入 Devtools。點(diǎn)擊紅框處的按鈕,就會(huì)打開一個(gè) nodejs 專用的調(diào)試窗口,并且代碼在第一行中斷了。
nodejs 調(diào)試窗口:
這個(gè)方式的實(shí)質(zhì)是,Chrome Devtool 根據(jù) v8引擎的調(diào)試協(xié)議 向 nodejs 進(jìn)程發(fā)送指令,控制代碼的運(yùn)行??梢园l(fā)現(xiàn),在網(wǎng)頁(yè)的調(diào)試中,Chrome 是接受指令的一方,而在 nodejs 調(diào)試中,Chrome 轉(zhuǎn)身變?yōu)榘l(fā)送指令的一方。所謂從悲慘的乙方華麗轉(zhuǎn)身成甲方。
node 默認(rèn)的 websocket 端口是 9229,如果有需要的話(比如端口被占用了),我們可以通過(guò)一些方式改變這個(gè)端口。
- node --inspect=9228 debug.js
- Debugger listening on ws://127.0.0.1:9228/30f21d45-9806-47b8-8a0b-5fb97cf8bb87
- For help, see: https://nodejs.org/en/docs/inspector
在我們打開 Devtool 時(shí),Chrome 默認(rèn)檢查 9229 端口,但當(dāng)我們改變了端口號(hào)后,就需要手動(dòng)去指定 Chrome 檢查的地址了。點(diǎn)擊下圖中的 Configure 按鈕,輸入 127.0.0.1:9228,然后點(diǎn)擊 Done。這時(shí)候 Remote Target 中就會(huì)出現(xiàn) 剛才啟動(dòng)的 node 進(jìn)程,點(diǎn)擊 inspect 就可以進(jìn)入調(diào)試了。
使用 VS Code 調(diào)試 Nodejs
到此為止,我們已經(jīng)達(dá)成調(diào)試 node 的目的,但還有些繁瑣,不夠自動(dòng)化。我們可以使用 VS Code,來(lái)一鍵啟動(dòng)調(diào)試。
用 VS Code 打開剛才的工程,然后在 launch.json 中輸入這些:
- {
- "version": "0.2.0",
- "configurations": [
- {
- "type": "pwa-node",
- "request": "launch",
- "name": "Launch Program",
- "skipFiles": [
- "<node_internals>/**"
- ],
- // ${file} 的意思是,當(dāng)我們啟動(dòng)調(diào)試的時(shí)候,調(diào)試的程序就是當(dāng)前 focus 的文件。
- "program": "${file}"
- }
- ]
- }
這時(shí)候切換到 index.js 文件,按 f5 啟動(dòng)調(diào)試程序,當(dāng)運(yùn)行到第二行 debugger 語(yǔ)句的時(shí)候,就會(huì)自動(dòng)暫停執(zhí)行。也可以點(diǎn)擊代碼行數(shù)的左側(cè)來(lái)打斷點(diǎn)。
另外,這個(gè)配置是支持 TypeScript 的,我們只需要 index.js 重命名為 index.ts,然后正常啟動(dòng)調(diào)試就行。
Conditional Breakpoint 條件斷點(diǎn)
在某些情況下,我們不希望打上的每個(gè)斷點(diǎn)都發(fā)揮作用,而是在執(zhí)行到斷點(diǎn)那行,且滿足某個(gè)條件再中斷代碼執(zhí)行。這就是條件斷點(diǎn)。
- for (let i = 0; i < 10; i++) {
- console.log("i", i);
- }
比如上面的代碼,假設(shè)我們?cè)诘诙?console.log 打了斷點(diǎn),那么這個(gè)斷點(diǎn)總計(jì)會(huì)中斷十次。這往往是我們不希望看到的,可能我們需要的僅僅是其中某一次循環(huán)而非所有。這時(shí)候可以右鍵點(diǎn)擊并選擇 Add Conditional Breakpoint。
這時(shí)會(huì)有一個(gè)輸入框出現(xiàn),我們?cè)谄渲休斎?i === 5。
這時(shí)候啟動(dòng)調(diào)試,就會(huì)跳過(guò) i 為 0 - 4,直接在在 i 為 5 的時(shí)候中斷代碼執(zhí)行?;謴?fù)代碼執(zhí)行后,會(huì)略過(guò) i 為 6 - 9 的情況。
Conditional Breakpoint 在調(diào)試帶有大量循環(huán)和 if else 判斷時(shí)極為有用,特別是當(dāng)某處的邏輯整體上是符合預(yù)期的,僅有個(gè)別特殊情況的輸出錯(cuò)誤,使用條件斷點(diǎn)就可以略過(guò)這些正常的情況,只在個(gè)別特殊情況出現(xiàn)的時(shí)候,再中斷執(zhí)行,供我們查看各個(gè)變量是否計(jì)算正常。
總結(jié)
調(diào)試是日常工作中非常重要的能力,因?yàn)槌碎_發(fā)新功能外,日常有很大一部分都在調(diào)整舊的代碼,處理特別條件下的邏輯錯(cuò)誤。熟練掌握調(diào)試可以很好地提升搬磚幸福感,一個(gè)復(fù)雜的 bug 卡幾小時(shí),很容易讓人心里崩潰。但也不是說(shuō)斷點(diǎn)調(diào)試是任何情況下都適用的銀彈,簡(jiǎn)單的邏輯還是可以愉快地 console.log 的。
文章介紹了使用 Chrome Devtools 和 VS Code 斷點(diǎn)調(diào)試的方法,整體上還是更推薦使用 VS Code。launch.json 只需要一次配置,后續(xù)都可以 f5 一鍵啟動(dòng)調(diào)試。另外,文中提到的各種 launch.json 文件的配置,都可以使用 VS Code 自帶的工具一鍵生成。只要打開 launch.json,編輯器的右下角就會(huì)出現(xiàn) Add Configuration 按鈕,點(diǎn)擊就可以選擇自己需要添加的調(diào)試配置。