調(diào)試工具的通用原理:調(diào)試四要素
作為前端開發(fā),調(diào)試是每天都會(huì)接觸的概念。你覺得什么是調(diào)試呢?
有同學(xué)說,我用 Chrome DevTools 調(diào)試網(wǎng)頁,可以查看元素,網(wǎng)絡(luò)請(qǐng)求,斷點(diǎn)運(yùn)行 JS,用 Performance 工具分析性能等,這是網(wǎng)頁的調(diào)試。
有同學(xué)說,我用 VSCode Debugger 調(diào)試 Node.js,可以同時(shí)調(diào)試多個(gè)進(jìn)程的代碼。這是 Node.js 的調(diào)試。
有同學(xué)說,我用 React DevTools 和 Vue DevTools 的 chrome 插件來調(diào)試 React、Vue 組件,還會(huì)用獨(dú)立的 React DevTools 調(diào)試 React Native 應(yīng)用。這是我常用的調(diào)試工具。
沒錯(cuò),這些都屬于調(diào)試。那它們有什么共同特點(diǎn)呢?
它們都是把運(yùn)行的狀態(tài)暴露給調(diào)試工具,做一些展示和交互。
所以,我們可以給調(diào)試下個(gè)定義:
代碼在某個(gè)平臺(tái)運(yùn)行,把運(yùn)行時(shí)的狀態(tài)通過某種方式暴露出來,傳遞給開發(fā)工具做 UI 的展示和交互,輔助開發(fā)者排查問題、梳理流程、了解代碼運(yùn)行狀態(tài)等,這個(gè)就是調(diào)試。
這里的某個(gè)平臺(tái),可以是瀏覽器、Node.js、Electron、小程序等任何能執(zhí)行 JS 代碼的平臺(tái)。
暴露出的運(yùn)行時(shí)狀態(tài),可能是調(diào)用棧、執(zhí)行上下文,或者 DOM 的結(jié)構(gòu),React 組件的狀態(tài)等。
暴露出這些數(shù)據(jù)的方式一般是通過基于 WebSocket 的調(diào)試協(xié)議,當(dāng)然也會(huì)有別的方式。
那常見的調(diào)試工具都是怎么實(shí)現(xiàn)的,有沒有什么通用的原理呢?
我們分別來看一下:
Chrome DevTools 原理
Chrome DevTools 分為兩部分,backend 和 frontend:
- backend 和 Chrome 集成,負(fù)責(zé)把 Chrome 的網(wǎng)頁運(yùn)行時(shí)狀態(tài)通過調(diào)試協(xié)議暴露出來。
- frontend 是獨(dú)立的,負(fù)責(zé)對(duì)接調(diào)試協(xié)議,做 UI 的展示和交互。
兩者之間的調(diào)試協(xié)議叫做 Chrome DevTools Protocol,簡(jiǎn)稱 CDP。
傳輸協(xié)議數(shù)據(jù)的方式叫做信道(message channel),有很多種,比如 Chrome DevTools 嵌入在 Chrome 里時(shí),兩者通過全局的函數(shù)通信;當(dāng) Chrome DevTools 遠(yuǎn)程調(diào)試某個(gè)目標(biāo)的代碼時(shí),兩者通過 WebSocket 通信。
frontend、backend、調(diào)試協(xié)議(CDP)、信道,這是 Chrome DevTools 的 4 個(gè)組成部分。
backend 可以是 Chromium,也可以是 Node.js 或者 V8,這些 JS 的運(yùn)行時(shí)都支持 Chrome DevTools Protocol。
這就是 Chrome DevTools 的調(diào)試原理。
除了 Chrome DevTools 之外,VSCode Debugger 也是常用的調(diào)試工具:
VSCode Debugger 原理
VSCode Debugger 的原理和 Chrome DevTools 差不多,也是分為 frontend、backend、調(diào)試協(xié)議這幾部分,只不過它多了一層適配器協(xié)議。
為了能直接用 Chrome DevTools 調(diào)試 Node.js 代碼,Node.js 6 以上就使用 Chrome DevTools Protocol 作為調(diào)試協(xié)議了,所以 VSCode Debugger 要調(diào)試 Node.js 也是通過這個(gè)協(xié)議。
但是中間多了一層適配器協(xié)議 Debug Adapter Protocol,這是為什么呢?
因?yàn)?VSCode 不是 JS 專用編輯器呀,它可能用來調(diào)試 Python 代碼、Rust 代碼等等,自然不能和某一種語言的調(diào)試協(xié)議深度耦合,所以多了一個(gè)適配器層。
這樣 VSCode Debugger 就可以用同一套 UI 和邏輯來調(diào)試各種語言的代碼,只要對(duì)接不同的 Debug Apapter 做協(xié)議轉(zhuǎn)換即可。
這樣還有另一個(gè)好處,就是別的編輯器也可以用這個(gè) Debug Adapter Protocol 來實(shí)現(xiàn)調(diào)試,這樣就可以直接復(fù)用 VSCode 的各種語言的 Debug Adapter 了。
VSCode Debugger 的 UI 的部分算是 frontend,而調(diào)試的目標(biāo)語言算是 backend 部分,中間也是通過 WebSocket 傳遞調(diào)試協(xié)議。
整體和 Chrome DevTools 的調(diào)試原理差不多,只不過為了支持 frontend 的跨語言復(fù)用,多了一層適配器層。
除了 Chrome DevTools 和 VSCode Debugger 外,平時(shí)我們開發(fā) Vue 或 React 應(yīng)用,還會(huì)用 Vue DevTools 和 React DevTools:
Vue/React DevTools
Vue DevTools 或者 React DevTools 都是以 Chrome 插件(Chrome Extension)的形式存在的,要搞懂它們的原理就得了解 Chrome 插件的機(jī)制。
Chrome 插件中可以訪問網(wǎng)頁的 DOM 的部分叫做 Content Script,隨頁面啟動(dòng)而生效,可以寫一些操作 DOM 的邏輯。還有一部分是后臺(tái)運(yùn)行的,叫做 Background,瀏覽器啟動(dòng)就生效了,生命周期比較長(zhǎng),可以做一些常駐的邏輯。
如果是擴(kuò)展 DevTools 的 Chrome 插件,那還有一部分 DevTools Page,是在 DevTools 里顯示的頁面:
Content Script 部分可以操作 DOM,可以監(jiān)聽 DOM Event。
Backgroud 部分可以訪問 extension api,可以和 Content Script 還有 DevTools Page 通信。
DevTools Page 部分可以訪問 devtools api,可以向當(dāng)前 window 注入 JS 執(zhí)行。
這就是 Chrome 插件的大概架構(gòu)。
Vue DevTools 和 React DevTools 就是基于這個(gè)架構(gòu)來實(shí)現(xiàn)的調(diào)試功能。
你看 Vue DevTools 的源碼目錄會(huì)發(fā)現(xiàn),它也是分為 backend 和 frontend 的
那 backend 運(yùn)行在哪,frontend 運(yùn)行在哪,兩者怎么通信呢?
DevTools Page 是可以在頁面 eval JS 的,那就可以注入 backend 的代碼。
backend 的代碼可以拿到 Vue 組件的信息,通過 window message 的方式傳遞給 BackGround。
BackGround 可以和 DevTools Page 通信,從而實(shí)現(xiàn)消息轉(zhuǎn)發(fā)。
DevTools Page 根據(jù)拿到的數(shù)據(jù),渲染組件的信息,實(shí)現(xiàn)交互功能。
React DevTools 也是類似的,都是通過 backend 拿到組件信息,然后傳遞給 DevTools Page 做渲染和交互。
不過 React DevTools 還有獨(dú)立的 Electron 應(yīng)用,可以用于 React Native 的調(diào)試。
這種自定義調(diào)試工具也是用的 Chrome DevTools Protocol 協(xié)議么?
明顯不是,CDP 協(xié)議用來調(diào)試 DOM、JS 等挺不錯(cuò)的,但是不好擴(kuò)展,如果有別的需求,一般都是自定義調(diào)試協(xié)議。
過了一遍 Chrome DevTools、VSCode Debugger、Vue/React DevTools 的原理,有沒有發(fā)現(xiàn)它們有一些相同的地方?
沒錯(cuò),都有 backend 部分負(fù)責(zé)拿到運(yùn)行時(shí)的信息,有 frontend 部分負(fù)責(zé)渲染和交互,也有調(diào)試協(xié)議用來規(guī)定不同數(shù)據(jù)的格式,還有不同的信道,比如 WebSocket 、Chrome 插件的 background 轉(zhuǎn)發(fā)等。
frontend、backend、調(diào)試協(xié)議、信道,這是調(diào)試工具的四要素。
不過,不同的調(diào)試工具都會(huì)有不同的設(shè)計(jì),比如 VSCode Debugger 為了跨語言復(fù)用,多了一層 Debugger Adapter,React DevTools 有獨(dú)立的 electron 應(yīng)用,用自定義調(diào)試協(xié)議,可以調(diào)試 React Native 代碼。
總結(jié)
我們會(huì)用 Chrome DevTools、VSCode Debugger、Vue/React DevTools 等工具來調(diào)試網(wǎng)頁、Node.js、React/Vue 的代碼,它們都屬于調(diào)試工具。
調(diào)試就是通過某種信道(比如 WebSocket)把運(yùn)行時(shí)信息傳遞給開發(fā)工具,做 UI 的展示和交互,輔助開發(fā)者排查問題、了解代碼運(yùn)行狀態(tài)等。
我們簡(jiǎn)單過了一遍這些調(diào)試工具的原理:
它們有通用的部分,都有 frontend、backend、調(diào)試協(xié)議、信道這四要素。
也有不同的部分,比如 VSCode Debugger 多了一層 Debugger Adapter,用于跨語言的復(fù)用,Vue/React DevTools 通過向頁面注入 backend 代碼,然后通過 Background 實(shí)現(xiàn)雙向通信等。
抓住它們相同的部分來分析,理解不同的部分的設(shè)計(jì)原因,就很容易搞懂各種調(diào)試工具的原理了。