瀏覽器斷點除錯js
說了一些 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 專業的培訓的人來說,是習以為常的事情,就像吃飯、呼吸一樣自然。然而最可怕的是:很多前端工程師都不知道。