一個關於如何使用調試來解決任何 JavaScript 問題的教程
調試是一個很好的技能,它可以幫助你解決 JavaScript 代碼中的每個問題。
每天早上我們起床,吃美味的早餐,坐在電腦前寫完美的代碼,這個夢想是理想的,不是嗎?但這只是一個夢想而已。
無論你有多好,無法寫出沒有缺陷的代碼。代碼總是有缺陷的,這是它的定義。
當你開始寫代碼時,你可能沒有見過或預料到錯誤。錯誤可能只有在你將程序發佈給用戶之後才會被發現,這是最糟糕的情況。
錯誤可能由你自己在測試程序時發現,甚至可能在某些事情正常運作時就突然出現故障,只因為你改變了一行代碼。
這些被稱為回歸錯誤。
作為開發人員,錯誤是我們日常工作的一部分,但我們的工作是盡可能地將它們減少到最低。
當你知道有錯誤時,你如何解決它呢?
嗯,最難的部分始終是確定錯誤發生在哪裡。
然後,第二個最困難的部分是找出為什麼會發生這個錯誤。
一旦你知道了上述所有問題的答案,解決錯誤通常就很容易了。
通常我們可以做兩件事來解決錯誤。
一種技術是非常基本的,涉及嘗試找出狀態(變量的內容)和程序流程的值,並將這些變量打印到日誌或程序的輸出中。
找出錯誤可能出現的地方
調試是一種對程序員活動非常核心的技能。
有時候我們盡力工作,但程序卻不能正常工作,例如,它崩潰了,只是運行得很慢,或者它打印了錯誤信息。
當你寫的程序不像你預期的那樣運行時,你會怎麼做?
你開始調試它。
第一步始終是觀察發生了什麼,並嘗試確定問題來自哪裡。
它是環境中的問題嗎?
它是你給程序的輸入中的問題嗎?
它是由於內存使用過多而導致的一次性崩潰嗎?
還是每次運行它都會發生?
這些都是在解決問題時開始朝著正確方向前進的重要信息。
一旦你對錯誤的來源有了一些想法,你就可以開始檢查該特定程式碼的部分了。
最簡單的調試方式,至少在工具方面而言,是讀出你所寫的代碼。大聲讀出來。
如果你讀的是一個字符串或一個數字,那麼聽起來沒有任何魔力。
然而,有時我們可以通過這種方式發現問題。
在這一步之後,就是使用一些工具的時候了。
你第一次接觸 alert()
和 console.log()
如果閱讀代碼對你來說毫無意義,那麼下一個合乎邏輯的步驟就是開始在代碼中添加幾行代碼,以幫助你找到問題所在。
在 JavaScript 前端代碼中,你通常會使用 alert()
和 console.log()
。
考慮以下代碼行:
const a = calculateA()
const b = calculateB()
const result = a + b
出於某種原因,我們不知道代碼的最終結果計算不正確,所以我們在計算結果之前添加 alert(a)
和 alert(b)
。
當瀏覽器執行代碼時,它會打開兩個警告對話框:
const a = calculateA()
const b = calculateB()
alert(a)
alert(b)
const result = a + b
如果你將 alert()
傳遞的是一個字符串或一個數字,這種方法是行得通的。
一旦你處理的是一個陣列或一個對象,alert()
就會變得太複雜了,你可以使用 console.log()
:
const a = calculateA()
const b = calculateB()
console.log(a)
console.log(b)
const result = a + b
該值將打印在瀏覽器開發者工具的 JavaScript 控制台中。
檢查對象
假設我們有這個對象 car
,但我們不知道它的內容,我們想要檢查它:
const car = {
color: "black",
manufacturer: "Ford",
model: "Fiesta",
}
我們有幾種方法可以做到這一點。
console.log
console.log(car)
console.dir
console.dir(car)
在 Node.js 中,你可以使用 colors
屬性將顏色顯示在終端:
console.dir(car, { colors: true })
JSON.stringify()
這會將對象打印為字符串表示:
JSON.stringify(car)
通過添加這些參數:
JSON.stringify(car, null, 2)
你可以使其打印得更好看。最後一個數字確定縮進中的空格數量:
JSON.stringify()
的好處是它可以在控制台之外工作,你也可以將對象打印到屏幕上。
使用循環迭代屬性
for...in
循環很方便,可以打印出對象的所有屬性,使用方法如下:
const inspect = (obj) => {
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log(`${prop}: ${obj[prop]}`)
}
}
}
inspect(car)
使用瀏覽器調試器
能夠調試不按照你期望工作的程序非常重要。
一個在找出錯誤源頭時幫助你的工具是使用調試器。
調試器是一個工具,可以由編程語言的編譯器或其周圍的工具提供。例如,微軟的 VS Code 編輯器提供了一個 JavaScript 調試器。
瀏覽器中還提供了另一個調試器。
使用調試器,你可以在任何想要的時候停止程序的運行,監視變量的內容,執行任何你想要的代碼,並逐行運行程序的執行。
在瀏覽器中,將 debugger
語句添加到代碼中,將會暫停瀏覽器渲染頁面並開始調試器。
調試器是瀏覽器開發者工具中最強大的工具,它可以在 Sources 面板中找到:
屏幕的頂部顯示了文件導航器。
你可以選擇任何文件,並在右側進行檢查。這對於設置斷點非常重要,我們稍後會看到。
底部是實際的調試器。
斷點
當瀏覽器加載頁面時,JavaScript 代碼會被執行,直到遇到斷點為止。
此時,執行將暫停,你可以檢查正在運行的程序的所有內容。
你可以檢查變量的值,並逐行恢復程序的執行。
但首先,斷點是什麼?在簡單的形式中,斷點是一個放在代碼中的 breakpoint
指令。當瀏覽器遇到它時,它停止執行。
這在開發時是一個很好的選擇。另一個選擇是在 Sources 面板中打開文件,點擊要添加斷點的行上的號碼:
再次點擊斷點將刪除它。
在添加斷點後,你可以重新加載頁面,當找到斷點時,代碼將停止執行。
當你添加斷點時,你可以在 Breakpoints 面板中看到 form.js
的第 7
行有斷點。你可以在這裡查看所有斷點,並暫時禁用它們。
還有其他類型的斷點:
- XHR/fetch 斷點:當發送任何網絡請求時觸發
- DOM 斷點:當 DOM 元素更改時觸發
- 事件監聽斷點:在發生某些事件時觸發,如鼠標點擊
作用域
在這個例子中,我在一個事件監聽器中設置了一個斷點,所以我必須提交一個表單才能觸發它:
現在,所有屬於作用域的變量都會被打印出來,包括它們的值。你可以通過雙擊它們來編輯這些變量。
監視變量和表達式
在 Scope 面板的右側存在 Watch 面板。
它有一個 +
按鈕,你可以使用它來添加任何表達式。例如,添加 name
將打印名稱變量的值,例如 Flavio
。你可以添加 name.toUpperCase()
,它將打印 FLAVIO
:
恢復執行
現在,所有腳本都停了下來,因為斷點停止了執行。
在“Paused on breakpoint”標語的上方有一組按鈕,允許你改變這種狀態。
第一個按鈕是藍色的。點擊它將恢復正常的腳本執行。
第二個按鈕是 step over,它會恢復執行,直到下一行,然後再次停止。
下一個按鈕執行了 step into 操作:進入正在執行的函數,讓你深入研究其中的細節。
step out 是相反的:返回到調用這個函數的外部函數。
這些是在調試期間控制流程的主要方法。
編輯腳本
從這個開發者工具頁面,你可以編輯任何腳本,也可以在腳本執行時編輯。只需編輯文件,然後在 Mac 上按 cmd-S,Windows/Linux 上按 ctrl-S。
當然,除非你是在本地工作並在開發者工具中設置了工作空間,否則這些更改不會持久保存。
檢查呼叫堆棧
呼叫堆棧 可以很好地顯示你深入 JavaScript 代碼的幾個函數級別。它可以讓你通過點擊每個函數名稱來上移:
打印堆棧跟踪
在某些情況下,打印函數的堆棧跟踪非常有用,也許是為了回答 “你是如何達到那部分代碼的?” 這個問題。
你可以使用 console.trace()
來這樣做:
const function2 = () => console.trace()
const function1 = () => function2()
function1()
記錄不同的錯誤級別
如前所述,console.log
很適合在 Console 中打印消息。
現在,我們將發現另外三個方便的方法,它們將幫助我們調試,因為它們總是隱含地指示出不同的錯誤級別。
首先是,console.info()
正如你所看到的,它旁邊打印了一個小的 i
,這表明這條日誌消息只是一個信息。
第二個是,console.warn()
它打印了一個黃色的驚嘆號。
如果你激活了 Console 的過濾工具欄,你可以看到 Console 允許你根據類型過濾消息,所以根據類型區分消息真的很方便,例如,如果我們現在點擊“Warnings”,所有不是警告的打印消息將被隱藏。
第三個函數是 console.error()
它與其他函數有所不同,因為除了打印出明確指出有錯誤的紅色 X 之外,還打印了生成錯誤的函數的完整堆棧跟踪,因此我們可以去嘗試解決它。
在導航期間保留日誌
除非在控制台設置中勾選了 Preserve log,否則在每次頁面導航時,控制台消息都會被清除:
分組控制台消息
控制台消息可能會變得很大,當你嘗試調試錯誤時,噪聲可能會讓你分心。
為了解決這個問題,控制台 API 提供了一個便利的功能:將控制台消息分組。
首先,讓我們做個例子。
console.group('Testing the location')
console.log('Location hash', location.hash)
console.log('Location hostname', location.hostname)
console.log('Location protocol', location.protocol)
console.groupEnd()
如你所見,控制台創建了一個組,並在這裡我們有 Log 消息。
你可以做同樣的事情,但輸出一個折疊的消息,你可以根據需要打開它,以進一步限制噪音:
console.groupCollapsed('Testing the location')
console.log('Location hash', location.hash)
console.log('Location hostname', location.hostname)
console.log('Location protocol', location.protocol)
console.groupEnd()
好處是這些組可以嵌套,因此你可以繼續做以下操作:
console.group('Main')
console.log('Test')
console.group('1')
console.log('1 text')
console.group('1a')
console.log('1a text')
console.groupEnd()
console.groupCollapsed('1b')
console.log('1b text')
console.groupEnd()
console.groupEnd()
黑盒腳本
在使用某些庫時,你可能不想 “進入” 它們,你相信它們,不希望在呼叫堆棧中看到它們的代碼,例如上面提到的 validator.min.js
,我用它來驗證電子郵件。
我相信它做得很好,所以我可以在呼叫堆棧中右鍵點擊它,然後點擊 Blackbox script。從那時起,不可能進入此腳本代碼,你很高興地只使用你自己的應用程序代碼工作。
使用瀏覽器開發者工具調試 Node.js
由於 Node.js 是建立在 Chrome 的相同引擎 v8 上的,你可以將這兩者連接起來,使用 Chrome DevTools 檢查 Node.js 應用程序的執行。
打開你的 終端 並運行:
node --inspect
然後在 Chrome 中輸入此 URL:about://inspect
。
點擊 Node 目標旁邊的 “Open dedicated DevTools for Node” 鏈接,你就可以在瀏覽器的開發者工具中訪問 Node.js:
確保你點擊這個鏈接,而不是下面的檢查鏈接,因為它會在我們重新啟動時自動重新連接到 Node.js 實例 - 非常方便!