注:本篇來自胡子大哈在知乎的回答,純屬轉載,侵刪!
據我觀察,大部分從非 CS 專業出身的前端工程師(甚至是 CS 專業的前端工程師)都不知道如何進行斷點調試。遇到 bug 的時候打滿屏的 `console.log` 半天還 debug 不出來(但在你學會斷點調試的時候,可能瞬間就精確定位 bug 了)。斷點調試這種最最最基本的技能居然在他們看來如此匪夷所思。
@戴嘉華
說了一些 Chrome 開發者工具的技巧,其實並沒有涉及到開發者工具最核心的功能之一:斷點調試。斷點可以讓程序運行到某一行的時候,把程序的整個運行狀態進行凍結。你可以清晰地看到到這一行的所有的作用域變量、函數參數、函數調用堆棧。你可以看到數據是怎么在程序當中流動的,你還可以修改、把玩它們。斷點調試讓你真正了解一個程序的運作流程。
聽聽亞洲舞王,著名 Web 前端工程師尼古拉斯·趙四是怎么說的:
“斷點調試是檢驗一個前端工程師 debug 能力的唯一標准;是從初級前端工程師成為中高級前端工程師的必經之路;是了解源碼和程序運行狀態的不二法門!”
看看 IBM 高級工程師,著名IT評論家,王博士怎么說:
“面試一個前端工程師的時候,我一般給他一個bug,然后看他怎么打斷點的,一個人的能力在調試過程中一覽無遺。”
除了 debug 的時候會用到,斷點調試在你了解一個第三方庫源碼也很有幫助。精通斷點調試的工程師在閱讀源碼的效率上比打 `console.log` 高效不止一個數量級。所以懂得如何斷點調試,你在 debug 能力、從源碼中學習新知識的能力(包括知識的量)會甩其他工程師好幾條街。
接下來是小白教程,大神繞路。
=============================== 分割線 ==============================
下面是日常打斷點直播,跟着我一步步來,看看斷點調試如何可以讓我們了解一個程序的運行狀態、流程。這里的例子是: 如何通過斷點調試,了解React.js 的 `setState` 方法到底干了什么事情。
建立一個 React 組件渲染到頁面上,App.js 如下:
import React, { Component } from 'react';
class App extends Component {
constructor () {
super()
this.state = { name: 'World'}
}
handleClick () {
this.setState({ name: 'World 2' })
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
Hello {this.state.name}
</div>
)
}
}
export default App
程序很簡單:點擊 div,通過 `setState` 讓原來 “Hello World” 顯示成 “Hello World 2”。
Chrome 打開頁面,F12 或者 option + command + j (Mac 下) 打開 Chrome 開發者工具。點擊第三個 tab : Sources。
然后 command + o(windows 應該是 control + o) ,輸入 App.js 查找我們的文件:
很棒,第一個就是。然后回車,就可以看到文件的內容:
很明顯,這就是我們上述的文件 App.js。點擊 div 的時候就調用 handleClick 方法。handleClick 方法內部有一個 setState 的操作,我們在第 10 行的行號上點一下。
然后點擊一下頁面上的 `Hello World`,
現在整個頁面卡住,什么都操作不了。這是因為我們的 JavaScript 已經完全在第 10 行暫停了,整個應用程序是凍結的狀態。看看代碼右邊區域:
Watch 是干嗎的?點擊 Watch 旁邊的➕,然后輸入 `this.state` 看看:
Watch 可以幫你監控執行當前斷點所在作用域任何表達式的執行結果,輸入 `this.state.name + 'ok'` 看看:
下面 Call Stack 表示這個函數的調用堆棧,也就是 setState 是被哪個上層函數調用的,上層函數又是誰調用的...
Scope 是當前斷點的作用域以及所有的閉包作用域以及全局作用域,你可以看到所有作用域的變量。
右邊的功能不多說了。我們看看怎么繼續打斷點,鼠標放到代碼里的 `setState` 函數上:
這里彈出的信息告訴我們,this.setState 是函數是在 `ReactComponent.js` 這個文件中定義的,而且還是個匿名函數,它接受兩個參數,一個是 partialState,一個是 callback。直接點擊 `ReactComponent.js`,就會到 `setState` 函數定義的地方:
我們看到 `setState` 的原型了,可以看到它其實最主要調用 `this.updater.enqueueSetState` 函數。我們在這一行上點打個斷點:
然后點擊瀏覽器上部的藍色箭頭:
這個箭頭指的是繼續執行代碼,代碼繼續執行,在我們新打的斷點的地方又停住了:
我們在旁邊可以看到我們傳進來的參數:
整個程序的變量猶如裸奔,一覽無遺。繼續把鼠標放到:
跳到 `ReactUpdateQueue.js` 文件:
繼續跳
繼續跳
你可以發現,到這里你可以了解到:原來 React.js 的組件實例有兩種,一種是公共實例 `publicInstance`,一種是內部實例 `internalIntance`,內部實例存放在 ReactInstanceMap 當中,每次 `setState` 的時候,先取到內部實例。回到 ReactUpdateQueue.js ,我們一怒之下又打了兩個斷點:
執行:
發現 React 把我們的新的 state push 到了一個叫 queue 的數組中。以下省略 N 次斷點,到了:
又省略了 N 次斷點
...
最后你會了解當點擊頁面的時候,React.js 執行了一個叫 `dispatchEvent` 的方法,然后會 `batchedUpdates` 里面執行 transaction.perform 一個事務;這個事務會執行一個函數,這個函數最終會調用我們的 handleClick 方法使其得以執行;setState 的時候會把新的 state 放到一個更新隊列里面,並且把 setState 組件放到一個叫 dirtyComponents 的隊列里面;
transaction.perform 每次會執行以后都會執行 closeAll,所以當上述的操作執行完以后,會把 transaction 每個 wrapper 都 close 掉。有一個 wapper 會調用 `runBatchedUpdates`,它會遍歷 `dirtyComponents` 然后一個個去執行 `updateComponent`,`updateComponent` 會從 state queue 拿出最新的 state 然后合並,最后更新頁面組件。
上面已經省略了很多細節。但是真實調試過程可能只需要 15 分鍾不到,你就可以大致理解到 React.js 到底了發生了什么事情。
這些事情你是用 console.log 沒法快速了解到,直接看源碼可能也會一頭霧水。但是斷點調試,方便、快捷、簡單。你值得擁有。但實際上,這些事情對於受過較為正統的 CS 專業的培訓的人來說,是習以為常的事情,就像吃飯、呼吸一樣自然。然而最可怕的是:很多前端工程師都不知道。