一個關於如何使用調試來解決任何 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.log 檢查對象

console.dir

console.dir(car)

使用 console.dir 檢查對象

在 Node.js 中,你可以使用 colors 屬性將顏色顯示在終端:

console.dir(car, { colors: true })

console.dir 在 Node 上顯示顏色

JSON.stringify()

這會將對象打印為字符串表示:

JSON.stringify(car)

使用 console.dir 檢查對象

通過添加這些參數:

JSON.stringify(car, null, 2)

你可以使其打印得更好看。最後一個數字確定縮進中的空格數量:

使用 console.dir 檢查對象

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

node-inspect

然後在 Chrome 中輸入此 URL:about://inspect

node-link-browser

點擊 Node 目標旁邊的 “Open dedicated DevTools for Node” 鏈接,你就可以在瀏覽器的開發者工具中訪問 Node.js:

node-console

確保你點擊這個鏈接,而不是下面的檢查鏈接,因為它會在我們重新啟動時自動重新連接到 Node.js 實例 - 非常方便!