Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 99视频在线,欧美视屏在线观看,国产性较精品视频免费

          整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          JavaScript基礎(chǔ)知識22-斷點調(diào)試

          嘍,你好啊,我是雷工!

          斷點調(diào)試是程序猿必備的調(diào)錯,梳理邏輯的技能;當(dāng)遇到程序報錯,或者程序邏輯理解不了,都可以通過斷點調(diào)試來輔助解決遇到的問題。

          斷點調(diào)試是程序猿必不可少的技能,本節(jié)學(xué)習(xí)斷點調(diào)試,以下為學(xué)習(xí)筆記。

          1、斷點調(diào)試

          ● 作用:學(xué)習(xí)時可以幫助更好地理解代碼運行,工作時可以更快找到bug

          ● 斷點調(diào)試步驟:

          1.1、選運行程序;

          1.2、在瀏覽器打開調(diào)試界面(按F12打開開發(fā)者工具)

          1.3、在瀏覽器控制臺中選中sources一欄;

          1.4、單擊對應(yīng)的html頁面;

          1.5、在代碼第一行位置處設(shè)置斷點(在需要設(shè)置斷點的對應(yīng)行上點擊鼠標(biāo)左鍵);

          1.6、重新刷新界面,執(zhí)行程序;

          1.7、手動讓程序逐行執(zhí)行,點擊F10或者點擊下一步按鈕。

          1.8、將鼠標(biāo)放到變量上或者某個條件上就可以看到執(zhí)行的結(jié)果了。

          ● 斷點:在某句代碼上加的標(biāo)記就叫斷點,當(dāng)程序執(zhí)行到這句有標(biāo)記的代碼時會暫停下來。


          2、循環(huán)嵌套:

          說明:一個循環(huán)中可以嵌套一個或多個循環(huán)。

          利用斷點調(diào)試,可以很好的理解循環(huán)嵌套程序。

          音小程序開發(fā)者工具(https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/developer-instrument/overview)是面向字節(jié)系小程序開發(fā)者推出的桌面端集成開發(fā)環(huán)境,支持小程序開發(fā)、調(diào)試、預(yù)覽、上傳等基本功能,旨在幫助開發(fā)者更高效地開發(fā)小程序,我也是負責(zé)本地開發(fā)能力的建設(shè)。

          因為工作原因最近對斷點調(diào)試進行一些研究,百度了一下,遺憾的是發(fā)現(xiàn)網(wǎng)絡(luò)上大部分內(nèi)容都是在教學(xué)如何使用調(diào)試工具,并沒有擴展到具體的細節(jié),譬如通信邏輯,基本原理等。因此,為了嘗試去弄懂一些斷點調(diào)試的底層邏輯,特意去找了一些英文文檔并實踐。

          前言

          作為一個前端開發(fā),前端調(diào)試的方式一般有如下幾種:

          1. 代碼中直接打印,比如很多時候直接在代碼中使用 console 來打印一些變量,或者在 vscode 中使用 Turbo Console Log 等插件生成特色的日志內(nèi)容。
          2. debugger,在代碼中輸入 debugger 關(guān)鍵字,然后在瀏覽器中進行斷點調(diào)試,或者在瀏覽器中找到源碼,然后進行斷點調(diào)試。
          3. vscode 自帶的 web 調(diào)試能力。

          相比于 console,debugger 可以看到代碼實際的執(zhí)行路線以及每個變量的變化,代碼可以跳著看,也可以針對某個函數(shù)步步執(zhí)行。

          但是 console 與 debugger 方式對代碼都有侵入,在開發(fā)階段可能要不斷增加和移除來調(diào)試,如果不小心忘了,那 mr 又得打回并重新提交了…

          相信很多人在提 mr 都有類似經(jīng)驗…

          相對來說,瀏覽器中找到 source 源碼打斷點是一個更好的方式,但是還是需要打開 Devtools ,并在 sources 面板找到文件注入斷點,操作上也是有點小麻煩。

          因此第 3 種方式,可能是不錯的方式,在 vscode 中直接在源碼中調(diào)試,并能看到具體的變量信息和網(wǎng)頁效果。

          實際上,瀏覽器打斷點與在 vscode 打斷點本質(zhì)原理都類似。下面就聊一聊瀏覽器斷點調(diào)試和 vscode 斷點調(diào)試的原理。

          基本知識

          Chrome Devtools Protocol

          在了解具體場景之前,首先有一個比較重要的概念,那就是 CDP。

          基本概念

          CDP(Chrome DevTools Protocol)是一種通過網(wǎng)絡(luò)協(xié)議與 Google Chrome 或其他兼容的瀏覽器進行通信的協(xié)議。通過 CDP,開發(fā)者可以遠程控制瀏覽器,獲取瀏覽器狀態(tài)信息,以及執(zhí)行各種瀏覽器操作,從而實現(xiàn)自動化測試、性能分析、調(diào)試等應(yīng)用場景。

          CDP 最早于 2011 年在 Chrome 15 版本中引入,作為 Chrome DevTools 的核心組件之一而出現(xiàn)。在此之前,開發(fā)者通常需要通過瀏覽器插件或者第三方工具來進行調(diào)試和測試,這些工具通常不夠標(biāo)準(zhǔn)化和通用,也難以實現(xiàn)遠程控制。

          就跟 Emoji 的歷史差不多了,都是亂的,然后規(guī)范化,最后大力發(fā)展。

          CDP 的出現(xiàn)解決了這些問題,使得開發(fā)者可以通過標(biāo)準(zhǔn)化的協(xié)議來遠程控制瀏覽器,獲取瀏覽器狀態(tài)信息,以及執(zhí)行各種瀏覽器操作。CDP 的出現(xiàn)和發(fā)展推動了 Web 開發(fā)和測試的發(fā)展,為開發(fā)者帶來了更加高效和便捷的開發(fā)和測試方式。

          CDP 通過 JSON-RPC 協(xié)議來進行通信,提供了一套完整的 API,包括 DOM、CSS、網(wǎng)絡(luò)、調(diào)試、安全等方面的接口。實際上,可以使用各種編程語言來編寫 CDP 客戶端,從而實現(xiàn)與瀏覽器的交互。

          上圖為 CDP 的官網(wǎng)(https://chromedevtools.github.io/devtools-protocol),可以看到,CDP 包括很多 Domains,常見的 CDP 信息包括:

          • DOM:提供了對文檔對象模型的訪問和操作接口,如節(jié)點遍歷、樣式計算、事件處理等。
          • CSS:提供了對樣式表的訪問和操作接口,如樣式計算、應(yīng)用、修改等。
          • Network:提供了對網(wǎng)絡(luò)請求和響應(yīng)的訪問和操作接口,如請求攔截、修改、模擬等。
          • Console:提供了對瀏覽器控制臺的訪問和操作接口,如日志記錄、錯誤捕獲、命令執(zhí)行等。
          • Debugger:提供了對瀏覽器調(diào)試器的訪問和操作接口,如斷點設(shè)置、單步執(zhí)行、變量查看等。
          • Performance:提供了對瀏覽器性能分析的訪問和操作接口,如性能指標(biāo)獲取、性能分析報告生成等。

          這幾個也是平常開發(fā)中最常用到的幾個 Domains 了。

          常見 CDP

          • Page:Page.navigate:頁面跳轉(zhuǎn)
          • Network:Network.enable:開啟網(wǎng)絡(luò),可以用來模擬網(wǎng)絡(luò)開閉能力
          • DOM:DOM.getDocument:獲取頁面樹,比如調(diào)試器 Elements 面板的展示
          • CSS:CSS.getComputedStyleForNode:返回給定 nodeId 的所有樣式,比如點擊 dom 節(jié)點,展示 css
          • Runtime:Runtime.evaluate:在當(dāng)前頁面中執(zhí)行 JavaScript 代碼
          • Debugger:下面會提到很多,譬如 Debugger.pause/Debugger.setXXX

          應(yīng)用場景

          chrome 的 Devtools (Front-End Devtools)與 Web Page 之間的調(diào)試也是通過 CDP 通信的,如下圖所示:

          除了調(diào)試,CDP 額外應(yīng)用場景也很多,比如剛才提到的自動化測試,通過 CDP 模擬用戶行為,操作頁面元素等,或者 CDP 獲取瀏覽器的性能指標(biāo)生成性能報告,還可以通過 CDP 模擬瀏覽器行為,獲取頁面數(shù)據(jù),實現(xiàn)爬蟲等等。

          瀏覽器斷點調(diào)試原理

          帶著問題出發(fā),可能需要搞懂以下 3 點:

          頁面與 Devtools 是如何通信的?

          斷點操作邏輯通信過程是什么?

          如何實現(xiàn)命中斷點并停止代碼執(zhí)行的?

          操作流程

          增加斷點

          在瀏覽器中,網(wǎng)頁的調(diào)試能力是由 Devtools 提供的。Devtools 與網(wǎng)頁之間的通信利用的是 Websocket,而通信協(xié)議則是 CDP。

          除了開發(fā)中常用到的元素高亮,日志打印和網(wǎng)絡(luò)審查,上面也提到了還可以在 sources 面板中使用 debugger。

          如下圖所示,找到一行 js 代碼,在代碼中點擊斷點調(diào)試,可以看到 Protocol Monitor 中有一些 CDP 消息,下面就來具體分析一下相關(guān) CDP 信息。

          為什么會發(fā)送多次,我也不理解,內(nèi)容基本上是一致的。

          點擊斷點以后,主要有以下一些 CDP 消息在頁面與 Devtools 之間通信:

          • Debugger.setBreakpointsActive:Activates / deactivates all breakpoints on the page.
          • Debugger.setBreakpointByUrl:Sets JavaScript breakpoint at given location specified either by URL or URL regex.
          • Debugger.getPossibleBreakpoints:Returns possible locations for breakpoint.

          setBreakpointsActive 表示告訴頁面要設(shè)置一個調(diào)試斷點了;setBreakpointByUrl 則是告訴頁面設(shè)置的具體信息;getPossibleBreakpoints 表示設(shè)置以后獲取正確的斷點位置,并展示藍色小塊。

          有時候可能會發(fā)現(xiàn)設(shè)置了某一行為斷點,但是斷點的位置并不是指向的位置,而是另外的位置。比如上面截圖,如果在 15 行設(shè)置斷點,則最后展示斷點位置為 18 行。

          整體流程如下圖:

          移除斷點

          除了在 sources 面板增加斷點,還可以取消斷點。取消斷點的 CDP 非常簡單, Devtools 會給 Web Page 發(fā)送一個 Debugger.removeBreakpoint 來移除斷點。

          實時斷點調(diào)試

          當(dāng)點擊完斷點以后,頁面會走到斷點所在的代碼位置,同時 Devtools 會接收到一些 CDP 消息,通知它當(dāng)前斷點的狀態(tài)和上下文信息。

          我寫了一個實例,是關(guān)于數(shù)字的增減邏輯,并在數(shù)字增加的時候,走到斷點位置(不需要刷新頁面)。

          可以看到,當(dāng)點擊 + 號以后,頁面就進入斷點調(diào)試邏輯,此時 Devtools 會收到 Debugger.paused消息:

          • Debugger.paused:Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.

          此時表示頁面已經(jīng)暫停了代碼執(zhí)行,Devtools 可以通過 Debugger.paused事件中的參數(shù),獲取當(dāng)前斷點的上下文信息,如斷點所在的函數(shù)、變量值、堆棧信息等。

          具體信息沒有對應(yīng)看

          點擊“Step Over next function call”(按鈕 1),Devtools 會收到 Debugger.resumed r??zu?m d 消息,通知繼續(xù)執(zhí)行代碼。

          • Debugger.resumed:Fired when the virtual machine resumed execution.

          隨后代碼跳到下一行,此時又會收到 Debugger.paused消息。

          點擊“Resume Script Execution” (按鈕 2)按鈕,Devtools 會收到 Debugger.resumed消息,如果還存在斷點,則此時也會收到 Debugger.paused消息。

          此外這里還有一個 Overlay.setPausedInDebuggerMessage 消息,為 Devtools 發(fā)送給頁面,其信息主要是讓頁面展示代碼停止?fàn)顟B(tài)下應(yīng)該展示的消息,默認(rèn)為 {"message":"Paused in debugger"},也就是如下圖展示的內(nèi)容:

          除了上面兩個按鈕,還有幾個調(diào)試按鈕,如下圖綠色區(qū)域內(nèi):

          分別是:Step into next function call、Step out of current function、Step、Deactivate breakpoints。

          Step into next function call:這個按鈕用于進入當(dāng)前行代碼所在的函數(shù)內(nèi)部,即單步進入函數(shù)中執(zhí)行。

          Step out of current function:這個按鈕用于跳出當(dāng)前函數(shù),即單步跳出當(dāng)前函數(shù)執(zhí)行。

          Step:這個按鈕用于單步執(zhí)行代碼,即逐行執(zhí)行代碼。

          Deactivate breakpoints:這個按鈕用于禁用所有的斷點,即暫停調(diào)試器的所有斷點。

          點擊“Step into next function call”,Devtools 會發(fā)送 Debugger.stepInto 消息,并收到 Debugger.resumedDebugger.paused消息,進入到函數(shù)內(nèi)部。

          • Debugger.stepInto:Steps into the function call.

          點擊“Step out of current function”,Devtools 會發(fā)送 Debugger.stepOut消息,并收到 Debugger.resumedDebugger.paused消息,跳出該函數(shù)。

          點擊 “Step” 按鈕,Devtools 則發(fā)送 Debugger.stepInto,代碼執(zhí)行到下一行,每次點擊,都會發(fā)送 Debugger.stepInto消息。

          點擊 “Deactivate (/?di??k.t?.ve?t/) breakpoints”,Devtools 則發(fā)送 Debugger.setBreakpointsActive 消息。如果當(dāng)前斷點狀態(tài)為執(zhí)行狀態(tài),則參數(shù)為 active: false,同時設(shè)置藍色小塊顏色為透明色。

          重新執(zhí)行代碼,斷點調(diào)試能力失效。

          再點擊一次,則參數(shù)為 active: true,斷點調(diào)試能力生效。

          基本通信源碼

          了解完相關(guān)斷點操作流程以后,再分析一下相關(guān)邏輯的源碼。

          首先,Devtools 的源碼就是 Front-End Devtools,UI 上的邏輯這里就不多分析。關(guān)于頁面的調(diào)試通信邏輯在 DebuggerModel 中:https://source.chromium.org/chromium/chromium/src/+/main:out/Debug/gen/third_party/devtools-frontend/src/front_end/core/sdk/DebuggerModel.js;l=280;drc=f09c12c84b39d13189a7039a05253ca3766d4751;bpv=0;bpt=0

           async stepInto() {
              const skipList = await this.computeAutoStepSkipList("StepInto" /* StepInto  /);  void this.agent.invoke_stepInto({ breakOnAsyncCall: false, skipList });  }  async stepOver() {   this.#autoSteppingContext = this.#debuggerPausedDetailsInternal?.callFrames[0]?.functionLocation() ?? null;  const skipList = await this.computeAutoStepSkipList("StepOver" /  StepOver  /);  void this.agent.invoke_stepOver({ skipList });  }  async stepOut() {   const skipList = await this.computeAutoStepSkipList("StepOut" /  StepOut */);
              if (skipList.length !== 0) {
                void this.agent.invoke_stepOver({ skipList });
              } else {
                void this.agent.invoke_stepOut();
              }
            }
            pause() {
              this.#isPausingInternal = true;
              this.skipAllPauses(false);
              void this.agent.invoke_pause();
            }
          

          很清晰的看到,上面提到的各種操作邏輯的函數(shù),譬如 pausestepXXX等 API。

          這里列舉幾個操作按鈕通信較多的 API。

          pause() 的主要邏輯為 2 點:

          1. 設(shè)置使頁面斷點暫停狀態(tài)為 ture。
          2. 發(fā)送 Debugger.paused消息到頁面。

          stepInto() 的主要邏輯為:

          1. 拿到跳轉(zhuǎn)的 skipList,它是一個字符串?dāng)?shù)組,用于指定要跳過的函數(shù)名稱列。在操作調(diào)試按鈕時,一般都是空數(shù)組。
          2. 發(fā)送 Debugger.stepInto消息到頁面。

          其他 API 邏輯類似。

          再分析一下 chromium /?kro?.mi.?m/ 中的斷點調(diào)試代碼邏輯。chromium 中發(fā)送 CDP 消息到 Devtools 的邏輯在 devtools_agent_host_impl中,而斷點調(diào)試邏輯在devtools_session文件中,通過 agent 的 DispatchProtocolMessage最后調(diào)用到 session 的 shoulSendOnIO函數(shù)。

          具體來說,這個函數(shù)接收一個包含 CDP 方法的 span 參數(shù),然后檢查該方法是否屬于一組特定的方法,如果是,則返回 true,表示該 CDP 消息需要轉(zhuǎn)發(fā)。

          DevToolsSession 是 Chromium 源碼中的一個類,代表一個 DevTools 會話。DevToolsSession 負責(zé)管理與 DevTools 和頁面之間的通信,包括上面提到的調(diào)試。

          bool ShouldSendOnIO(crdtp::span<uint8_t> method) {
            static auto* kEntries = new std::vector<crdtp::span<uint8_t>>{
                crdtp::SpanFrom("Debugger.getPossibleBreakpoints"),
                crdtp::SpanFrom("Debugger.getScriptSource"),
                crdtp::SpanFrom("Debugger.getStackTrace"),
                crdtp::SpanFrom("Debugger.pause"),
                crdtp::SpanFrom("Debugger.removeBreakpoint"),
                crdtp::SpanFrom("Debugger.resume"),
                crdtp::SpanFrom("Debugger.setBreakpoint"),
                crdtp::SpanFrom("Debugger.setBreakpointByUrl"),
                crdtp::SpanFrom("Debugger.setBreakpointsActive"),
                crdtp::SpanFrom("Emulation.setScriptExecutionDisabled"),
                crdtp::SpanFrom("Page.crash"),
                crdtp::SpanFrom("Performance.getMetrics"),
                crdtp::SpanFrom("Runtime.terminateExecution"),
            };
            ...
          }
          

          可以看到,這里定義了所有發(fā)送到 Devtools 的 API。在 chromium 的各種斷點調(diào)試方法,最后都會調(diào)用 DispatchToAgent方法,并走到 ShouldSendOnIO邏輯。

          命中斷點

          通過上面的分析,了解到了調(diào)試器和頁面之間的 CDP 通信內(nèi)容和 API 的基本實現(xiàn)。那 chromium 又是如何停止代碼到斷點的呢?為何可以停止代碼執(zhí)行呢?

          在 DevTools 中,停止代碼執(zhí)行到斷點的核心實現(xiàn)是通過使用 V8 JS 引擎中的斷點機制來實現(xiàn)的。當(dāng) chromium 執(zhí)行到一個斷點時,V8 會暫停 JS 代碼的執(zhí)行,并將控制權(quán)轉(zhuǎn)交給 Devtools。這時候,Devtools 可以執(zhí)行上述提到的斷點調(diào)試的各種操作。

          這塊邏輯的代碼在 chromium auction_v8_devtools_agentauction_v8_devtools_session 中,看起來比較復(fù)雜,涉及到 AuctionV8DevToolsSession 和 AuctionV8DevToolsAgent 兩個類,我的理解是 DevtoolsAgent 提供了一些 Devtools debugger 的服務(wù),并找到對應(yīng)的 DevtoolsSession 進行通信。V8 將 ws 格式信息轉(zhuǎn)交給了 DevtoolsSession,最后通過 DevtoolsAgent 發(fā)送到了 Devtools。

          大概邏輯如下:

          通過 Devtools Agent,負責(zé)接收 Devtools 通信信息,并將斷點信息移交給 V8,然后由 V8 來對代碼進行停止操作。

          V8 里面的邏輯我只能看一個大概,整體邏輯如下:

          V8Debugger 是一個抽象,V8DebuggerAgentImpl 類實現(xiàn)了這個類,它是 Debug 類和 V8 調(diào)試協(xié)議之間的中介,負責(zé)將調(diào)試消息轉(zhuǎn)換為 V8 調(diào)試協(xié)議中定義的格式。

          關(guān)于 V8 斷點 Debugger 更底層的邏輯是與 os、cpu 相關(guān),os 提供了系統(tǒng)調(diào)用來實現(xiàn)可執(zhí)行代碼的中斷。

          中斷則是 cpu 執(zhí)行下一條指令之前,關(guān)注一下中斷標(biāo)記,從而判斷是否需要中斷執(zhí)行。整體邏輯上對照著 Vue 的渲染原理即可,每次事件循環(huán)結(jié)束后最后去走一次渲染 DOM。

          V8 本身也是將 JS 轉(zhuǎn)為可執(zhí)行語言,這也就是為何 JS 可以在瀏覽器中擁有斷點能力了。

          這里涉及到一些指令操作,沒有深究。

          同時,V8 中斷代碼執(zhí)行,也會提供一些環(huán)境數(shù)據(jù)到 Devtools,譬如當(dāng)前變量數(shù)值等,這時候 V8 就會將這些調(diào)試信息通過 V8 Debug Protocol 協(xié)議的格式丟給 Debug,最后丟給 Devtools,從而鼠標(biāo)懸浮在 sources panel 即可看到對應(yīng)的數(shù)據(jù)內(nèi)容。

          Debugger.evaluateOnCallFrameRuntime.getProperties 可以拿到一些環(huán)境信息,前者比如一些 number 數(shù)字就可以得到。

          Vscode Web 代碼斷點調(diào)試原理

          在 Vscode 中調(diào)試代碼,能讓開發(fā)者專注于代碼本身,一邊開發(fā)運行一邊斷點調(diào)試查看變量信息,并減少一些臟代碼的開發(fā)。如下圖所示,可以看到,似乎是將瀏覽器的 Debugger 的邏輯照搬到了 Vscode 中。

          在介紹完瀏覽器斷點調(diào)試的邏輯以后,我們大概了解了頁面與 Devtools 的通信過程和相關(guān) CDP 信息。有了這些基礎(chǔ),我們再分析分析 Vscode 中是如何實現(xiàn)斷點調(diào)試 Web 代碼的。

          launch.json

          在 Vscode 中配置調(diào)試后,會生成一個 .vscode/launch.json 文件,其主要是配置需要調(diào)試的 url 和遠程調(diào)試的端口號 port。

          {
            "version": "0.2.0",
            "configurations": [
              {
                "type": "chrome",
                "request": "launch",
                "name": "針對 localhost 啟動 Chrome",
                "url": "http://localhost:8080",
                "webRoot": "${workspaceFolder}"
              }
            ]
          }
          

          Debugging Architecture of VS Code

          [?ɑrk??tekt??r]

          Vscode 并不只是前端開發(fā)者調(diào)試 JS 使用,還可以調(diào)試其他語言,Python 一些教程就建議使用 Vscode 調(diào)試。因此 Vscode 的調(diào)試架構(gòu)高度靈活,可以支持多種編程語言和調(diào)試場景,并且可以基于該架構(gòu)實現(xiàn)各種調(diào)試擴展。

          如上圖,Vscode 的調(diào)試架構(gòu)中,有 3 個 Core Module:

          • Debug Adapter:調(diào)試適配器是 Vscode 和具體調(diào)試目標(biāo)之間的橋梁。適配器主要就是負責(zé)將調(diào)試請求轉(zhuǎn)換為調(diào)試目標(biāo),并將調(diào)試目標(biāo)轉(zhuǎn)為調(diào)試器需要的結(jié)果,其通過 Vscode Debug Protocol 協(xié)議通信。Debug Adapter 提供了一組標(biāo)準(zhǔn)的調(diào)試接口,包括設(shè)置斷點、單步執(zhí)行、查看變量值等。
          • Debug Extension:調(diào)試擴展是 Vscode 內(nèi)置的插件,提供特定語言或者場景的實現(xiàn)。比如可以調(diào)試 JS TS Python 等,同時社區(qū)也可以提供相關(guān)擴展,譬如 Java:
          • Debug UI:即 Vscode 的操作界面,它提供了調(diào)試器的各種操作和功能,例如設(shè)置斷點、單步調(diào)試、查看變量值等。

          :別忘了另外一個 Debugger,即為 launch.json 中的 type,指底層的調(diào)試目標(biāo),例如 Node.js 運行時、Chrome 瀏覽器等等。比如斷點后的信息需要傳遞給 chrome,需要去暫定代碼執(zhí)行,并斷點逐步執(zhí)行等。

          原理

          在了解原理之前,先看一些現(xiàn)象:

          1. 當(dāng) Vscode 啟動調(diào)試并走到指定斷點時,Chrome 自身調(diào)試器也會走到對應(yīng)的調(diào)試邏輯(Devtools 本身也是一個 ws client,任何 client 都會收到 chrome 的 cdp 消息)。
          2. 當(dāng)在 Vscode 調(diào)試面板操作 StepInto 按鈕時,Vscode 代碼會走到下一步,同時 chrome 調(diào)試也會走到下一步。
          3. 當(dāng)在 Chrome Devtools 中操作 StepInto 按鈕時,Chrome Devtools 代碼也會走到下一步,同時 Vscode 中代碼也會跳轉(zhuǎn)到下一步。

          通過上面 3 種現(xiàn)象可以看出,Vscode Webpage Devtools 關(guān)系如下:

          細品一下,這時候就可以知道為何需要 Debug Adapter 了。實際上,就是將 CDP 消息轉(zhuǎn)為 DAP。

          Workflow

          Vscode Chrome Debug 的工作流程如下:

          1. Vscode 啟動 JavaScript 調(diào)試器,并配置調(diào)試器相關(guān)的參數(shù),例如調(diào)試類型、調(diào)試目標(biāo)等等。
          2. JavaScript Debug Extension 會根據(jù)配置啟動一個 Debug Adapter 進程,并向該進程發(fā)送調(diào)試請求,請求 Debug Adapter 與 Debugger 之間建立連接。
          3. Debug Adapter 進程會根據(jù)用戶的配置,啟動相應(yīng)的 Chrome,并與對應(yīng)網(wǎng)頁(Debugger)建立連接。
          4. Debug Adapter 進程會將調(diào)試結(jié)果轉(zhuǎn)換為 Vscode 支持的調(diào)試消息格式,即上面提到的 DAP,并將調(diào)試消息發(fā)送給 Vscode。

          這里的核心就是 Extension,其作用就是調(diào)度與控制,比如啟動 Adapter 進程,發(fā)送與接收調(diào)試信息等等,屬于大 BOSS,而 Adapter 只是下屬。

          JS Debug Extension

          上面提到,chromium 內(nèi)部是使用 CDP 協(xié)議通信,因此 Extension 想要正確調(diào)試 Chrome WebPage,首先就得遵守 Chrome 的玩法。比如,在 Vscode 中點擊 StepInto 按鈕,這時候會將對應(yīng)操作信息轉(zhuǎn)化為 CDP 信息,然后再發(fā)送給 WebPage。

          Extension 啟動 Chrome 的邏輯在 companionBrowserLaunch 中:https://github.com/microsoft/vscode-js-debug/blob/main/src/ui/companionBrowserLaunch.ts#L50

          await vscode.commands.executeCommand('js-debug-companion.launchAndAttach', {
            proxyUri: tunnel ? 127.0.0.1:${tunnel.localAddress.port} : 127.0.0.1:${args.serverPort},
            wslInfo: process.env.WSL_DISTRO_NAME && {
              execPath: process.execPath,
              distro: process.env.WSL_DISTRO_NAME,
              user: process.env.USER,
            },
            ...args,
          });
          

          另外,Devtools 與 WebPage 是通過 ws 通信的,這里 JavaScript Extension 內(nèi)部實現(xiàn)與開發(fā)者工具調(diào)試器和模擬器的通信相似, Extension 與 WebPage 通信也是拿到了頁面的 debug ws url,在 Extension 內(nèi)部創(chuàng)建一個 ws client,通過該 client 監(jiān)聽來自于 WebPage CDP 信息,并轉(zhuǎn)發(fā)到會話的 Adapter,最后再交給 Vscode。

          看最新的代碼,JS Debug Extension 也會負責(zé)部分調(diào)試 UI 相關(guān)邏輯。

          Command 實例

          StepInto舉例,在 Vscode 中點擊該按鈕以后,會發(fā)送一個 DAP 消息:

          {
              "command": "stepInTo",
              "seq": number,
              "type": "request",
              "arguments": {
                  "threadId": number
              }
          }
          

          然后,Exetension 將該消息轉(zhuǎn)為 CDP 消息,并發(fā)送給 WebPage:

          {
              "id": 1,
              "method": "Debugger.stepInto",
              "params": {
                  "callFrameId": number/string
              }
          }
          

          WebPage 收到該消息后,返回執(zhí)行結(jié)果到 Extension:

          {
              "id": 1,
              "result": {}
          }
          

          Extension 再將該 response 通過 Debug Adapter 轉(zhuǎn)給 Vscode,Vscode 調(diào)整 UI:

          {
            "body": {
                "reason": "OK",
                "threadId": number
            },
            "type": "response"
          }
          

          相關(guān) DAP 格式可以在 debug-adapter-protocol 查閱:https://microsoft.github.io/debug-adapter-protocol/overview

          如果要在 Vscode 中查看實時的 DAP 和 CDP 消息,可以通過如下操作:

          源碼調(diào)試

          上面給到的例子非常簡單,js 代碼也沒有經(jīng)過構(gòu)建生成編譯后的代碼。但是實際場景中開發(fā)的項目會引入各種開源庫,然后經(jīng)過諸如 Webpack 等打包構(gòu)建工具做編譯打包,才能在瀏覽器中運行。編譯壓縮后的代碼一般不具備可讀性,因此在編譯后代碼進行調(diào)試成本比較高。

          We all know,SourceMap 存儲著源碼和生產(chǎn)代碼之間的映射關(guān)系。譬如我這里啟動了一個 Vite 項目:

          當(dāng)我在源碼的 main.ts 中設(shè)置斷點時,可以看到 Request 中的 url 為 host:port/src/main.ts,即實際傳給 WebPage 的斷點文件為編譯后的文件。

          JS Debug Extension 亦是如此。

          當(dāng)在 Vscode 的源碼中增加了一個斷點,JS Debug Extension 會根據(jù) sourceMap 將源代碼路徑映射到編譯后的代碼路徑中,并將這個信息發(fā)送給瀏覽器。

          所以呀,解析是前端行為。

          擴展:SourceMap 加載

          SourceMap 雖然也是靜態(tài)資源,但是其加載在 Network 面板并不能看到,而是在 Developer Resources 中。

          為了啟動快,我用的 Vite 來生成項目。Vite 利用了瀏覽器原生的 ES modules 功能,根據(jù)文件依賴關(guān)系,生成依賴樹,然后各模塊文件模塊單獨加載。Vite 文件都有單獨的 SourceMap,不需要配 SourceMap 依賴。

          可以看到,這里 Vite 默認(rèn)是直接內(nèi)嵌的 SourceMap,無需單獨請求, 可以在代碼文件加載完成后,就直接解析了,紅框里面展示的鏈接就是 Base64 的形式了。

          ??SourceMap 的解析是交給 Devtools 本身的,Debugger 只負責(zé)運行和暫停。因此,如果斷點在 SourceMap 解析完成之前觸發(fā),則沒法告訴 Debugger 正確的地址,可能會出現(xiàn)斷點無效情況。

          IDE 小程序斷點調(diào)試

          Devtools Debug

          根據(jù)上面的介紹,小程序斷點調(diào)試的最簡單辦法就是在代碼中寫上 debugger,然后交給 v8 處理即可。另外還有一種方式就是打開小程序調(diào)試器,在 sources panel 中打斷點,如下圖:

          打斷點,刷新小程序,即可跳轉(zhuǎn)到斷點位置。此時可以看到對應(yīng)的 CDP 消息中的 Request。

          可以看到,這里點擊的是 56 行,但實際上 Request 中卻不是,Devtools 通過 sourceMap 進行了處理,定位到了 64 行。根據(jù)上面提到的源碼調(diào)試邏輯,這里的位置為編譯后的代碼位置,找到編譯產(chǎn)物代碼 app.js 即可看到 real position。

          IDE Editor Debug???

          考慮到上面提到的 Vscode 有 web 斷點調(diào)試能力,那 IDE Editor 或許也是可以支持?jǐn)帱c調(diào)試能力的。

          Vscode 可以直接在編輯器運行項目,然后啟動自定義的調(diào)試目標(biāo)(Debugger)。

          IDE 為小程序運行時的載體,與 Vscode 啟動 web 項目不一樣,其邏輯為編譯完成后生成一個編譯產(chǎn)物目錄,通過靜態(tài)服務(wù),Simulator 直接加載對應(yīng)編譯產(chǎn)物。因此,IDE 的 Editor 實際上跟 Simulator 沒什么聯(lián)系的。

          假設(shè)借用 Devtools Debug 的邏輯,當(dāng)在 Editor 打斷點時,捕獲所有的斷點 DAP 消息,當(dāng)開啟調(diào)試時,刷新模擬器,將所有的斷點信息轉(zhuǎn)為 CDP 信息發(fā)送給模擬器,或許就可以簡單實現(xiàn)該能力。

          當(dāng)然,考慮到是在源碼中打斷點,這里的難點應(yīng)該是在于要實現(xiàn) sourceMap 解析,而 Debug UI 則可以利用 Vscode JS Extension,或者通過自定義實現(xiàn)一個 Debug UI。

          總結(jié)

          本文從抖音開發(fā)者工具支持?jǐn)帱c調(diào)試能力需求引入,概述了瀏覽器斷點調(diào)試的基本原理,也介紹了 Vscode Web 代碼斷點調(diào)試能力,詳細介紹了各模塊中各 CDP 消息通信邏輯。閱讀本文可以掌握前端各種調(diào)試方法的基本原理。

          加入我們

          抖音開放平臺提供小程序、移動應(yīng)用、網(wǎng)站應(yīng)用、直播小玩法等多業(yè)務(wù)載體,為開發(fā)者提供豐富的能力和解決方案。抖音開放平臺基于平臺規(guī)則和開發(fā)者訴求,提供了兩種開放模式:能力開放和行業(yè)開放。

          • 在能力開放方面,平臺提供了運營抖音號、頭條號的相關(guān)能力,如發(fā)布視頻和獲取視頻數(shù)據(jù)、用戶數(shù)據(jù)和各類榜單信息等。同時也圍繞抖音小程序提供了擔(dān)保支付、客服管理、訂閱消息等基礎(chǔ)能力,以及短視頻 / 直播間掛載小程序、流量主、廣告主等經(jīng)營能力。
          • 在行業(yè)開放方面,平臺基于挖掘行業(yè)痛點、解決實際問題角度出發(fā),提供了到店餐飲、機酒旅、泛知識等多行業(yè)解決方案,提升品牌及商家的經(jīng)營收入和行業(yè)競爭力。

          參考文檔

          [1]

          V8 本地調(diào)試: https://zhuanlan.zhihu.com/p/568432229

          [2]

          Debugging over the V8 Inspector Protocol: https://v8.dev/docs/inspector

          [3]

          Adapter Debug Protocol: https://microsoft.github.io/debug-adapter-protocol/

          [4]

          SourceMap: https://zhuanlan.zhihu.com/p/615279891

          作者:Rabbitzzc

          來源:微信公眾號:字節(jié)前端 ByteFE

          出處:https://mp.weixin.qq.com/s/DGSSDEmAdj8sE_KfN3wQsg


          者:陳亦濤來源:大轉(zhuǎn)轉(zhuǎn)FE

          為什么要使用 debugger

          這篇文章將介紹如何使用斷點來進行 JavaScript 調(diào)試。在讀這篇文章之前,需要問一個問題:為什么要使用斷點來進行調(diào)試?

          我們首先需要認(rèn)可使用斷點的是必要的,否則下文介紹的所有斷點調(diào)試方法都會是廢話。console.log 是前端開發(fā)最常用的調(diào)試手段,它簡單直接解決一部分問題。但當(dāng)遇到十分復(fù)雜的問題,console.log 就會變得不趁手。比如:

          • 一個邏輯復(fù)雜的算法

          如果你刷過 leetcode 一定深有體會,算法某個測試用例報錯了,有時很難光靠目測找出有問題的那個方法。

          • 一個復(fù)現(xiàn)步驟十分繁瑣的bug。

          花了10分鐘好不容易復(fù)現(xiàn)了,但是只跟蹤到某行代碼,需要第二次添加 log 才能繼續(xù)尋找問題。查看log -> 添加log -> 查看log... 這個過程重復(fù)幾遍,今天剩下的磚就搬不完了。

          • 一段運行流程冗長的代碼
          • 一段沒有注釋、起名隨意的代碼
          • server 端代碼

          有 nodejs 服務(wù)端開發(fā)經(jīng)驗的同學(xué)相信有過在 postman 和 ide 之間反復(fù)橫跳的經(jīng)歷,如果光靠 log,對于一個巨大的復(fù)雜對象,控制臺是不好查看全貌的。如果一個接口還涉及到數(shù)據(jù)庫增刪、第三方依賴,那么復(fù)原上一次請求造成的后果也是一件痛苦的事情。

          在這些情況下,斷點調(diào)試是非常有價值的,將 debug 的時間復(fù)雜度從 O(n) 降到 O(1),讓搬磚更快樂。

          這是文章的內(nèi)容大綱:

          • Chrome debugger 基本用法
          • VS Code 調(diào)試 SPA 應(yīng)用
          • Chrome 調(diào)試 Nodejs
          • VS Code 調(diào)試 Nodejs


          Chrome debugger 基本用法

          最簡單的斷點調(diào)試,就是在代碼中加一句 debugger,然后到瀏覽器中刷新頁面,這時候瀏覽器就會在 debugger 語句那停止執(zhí)行。

          為了方便理解,引入一個簡單例子,在一個文件夾中創(chuàng)建 index.html 和 index.js,然后在 index.html 中引入 index.js。index.js 內(nèi)容如下:

          // 國際慣例,hello world。 
          const greet = () => { 
            const greeting = "hello debugger"; 
            // 瀏覽器執(zhí)行到這里將會暫停 
            debugger 
            console.log(greeting); 
          }; 
           
          greet(); 
           
          console.log("js evaluation done");

          執(zhí)行命令:

          npm i -g serve 
          serve .

          然后訪問 http://localhost:5000并打開開發(fā)者工具。

          這時候我們的 hello world 斷點就打上了,就像這樣:

          圖中分為四個區(qū)域,藍色區(qū)域用于文件選擇,Page 一欄是指當(dāng)前頁面中的 JS 文件,F(xiàn)ilesystem 會顯示我們系統(tǒng)中的文件。通常我們使用 Page。

          粉色是代碼的行號和內(nèi)容。代碼的行號處可以通過點擊來添加新的斷點,再次點擊后取消。

          黃色區(qū)域用于控制代碼的執(zhí)行,只需要掌握前四個按鈕的含義,就可以應(yīng)付絕大多數(shù)場景。按鈕1是讓代碼繼續(xù)執(zhí)行(resume),如果遇到下一個斷點就會再次中斷執(zhí)行。按鈕2可以讓瀏覽器執(zhí)行當(dāng)前行(圖中是第3行),然后在下一行中斷代碼,按鈕3是進入當(dāng)前函數(shù),查看函數(shù)具體內(nèi)容。假設(shè)我們當(dāng)前停在第7行 greet() ,點擊按鈕3就會進入 greet 方法中(也就是第2行)。如果不想再看 greet 方法了,就點擊按鈕4,跳出這個方法,回到第8行。

          綠色區(qū)域可以查看變量的內(nèi)容和當(dāng)前的調(diào)用棧。

          debugger 是最簡單粗暴的打斷點方式,但是需要修改我們的代碼。需要注意的是,上線前必須刪除這些語句。也可以通過配置 webpack 來自動去除。不過終究還是有些不方便,所以我們來看下如何通過 vscode 來簡化打斷點的方式。


          VS Code 調(diào)試 SPA 應(yīng)用

          首先我們使用 Vite 來創(chuàng)建一個 Vue 應(yīng)用用于演示(React步驟類似)。

          # 創(chuàng)建 vut-ts 應(yīng)用 
          npm init vite 
          cd hello-vite 
          npm install 
          # 調(diào)用 VS Code cli 打開項目, 
          # 或者手動在 VS Code 打開。 
          code . 
          npm run dev

          然后在 VS Code 中新建一個文件 .vscode/launch.json,填入這些內(nèi)容:

          { 
            "version": "0.2.0", 
            "configurations": [ 
              { 
                "type": "pwa-chrome", 
                "request": "launch", 
                "name": "Launch Vue project", 
                // 這里填入項目的訪問地址 
                "url": "http://localhost:3000", 
                "webRoot": "${workspaceFolder}" 
              }, 
            ] 
          }

          然后使用 cmd+q 退出你正在運行的 Chrome(這步很重要,不能跳過),按 f5 啟動 VS Code 的調(diào)試功能。VS Code 就會幫你啟動一個 Chrome 窗口,并訪問上述配置的中的 url。這時候我們的斷點就生效了,可以一步一步地控制代碼的運行,找出 bug 來源。

          這里有一個實用的小技巧,就是在 BREAKPOINTS 中,把 Uncaught Exceptions 勾上,這樣在代碼報錯的地方,就會自動中斷執(zhí)行。當(dāng)我們遇到一個報錯時,采用這個方法可以省去定位問題代碼的時間。

          另外我們可以發(fā)現(xiàn),在 VS Code 斷點生效時,Chrome Devtools 也會同步這個展示這個斷點。

          在 VS Code 中,調(diào)試有兩種模式,分別是 launch 和 attach。由于真正執(zhí)行代碼的是 Chrome 中的 JS 引擎,所以是否中斷代碼的控制權(quán)是在 Chrome 手里的。那為什么 VS Code 的斷點可以控制代碼的中斷呢?是因為 VS Code 通過 devtools-protocol 向 Chrome 發(fā)起指令,告訴 Chrome 需要在哪一行代碼暫停執(zhí)行。這個發(fā)送指令的過程,被稱作 attach。而 launch 的過程包含 attach ,即先 launch(啟動) 瀏覽器,然后 attach(附加) 斷點信息。所以 attach 模式是 launch 模式的子集。

          聽起來好像 launch 模式會更方便,為我們省去了手動啟動瀏覽器的過程。但是這存在一個問題,如果同時開發(fā)多個前端工程會怎樣?每個工程啟動一個調(diào)試進程,就會打開多個瀏覽器,那么在多個瀏覽器之間切換就會顯得很麻煩。我們可以使用 attach 模式解決這個問題。

          首先我們使用命令行啟動 Chrome。使用命令行的原因是,我們需要給 Chrome 的啟動傳參。

          # 運行這條命令前需要cmd+q退出已運行的Chrome 
          /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 
          # 如果看到這個輸出,說明傳參成功。 
          DevTools listening on ws://127.0.0.1:9222/devtools/browser/856a3533-ca5c-474f-a0cf-88b7ae94c75b

          VS Code 和 Chrome 是通過 websocket 交流,--remote-debugging-port 指定了 websocket 使用的端口。然后我們將 launch.json 文件修改成這樣:

          { 
            "version": "0.2.0", 
            "configurations": [ 
              { 
                "type": "pwa-chrome", 
                "request": "attach", 
                "name": "Vue Application", 
                // 項目訪問的 url 
                "url": "http://localhost:3000", 
                // websocket 端口,需要與 --remote-debugging-port 參數(shù)保持一致。 
                "port": 9222, 
                "webRoot": "${workspaceFolder}" 
              }, 
            ] 
          }

          注意在啟動 VS Code 調(diào)試之前,需要在 Chrome 中打開 http://localhost:3000 這個頁面。然后我們在 VS Code 中打上斷點,刷新瀏覽器,代碼就成功停在斷點處了。第二個、第n個工程都可以采用相同的配置,區(qū)別是 url 字段要根據(jù)項目配置進行修改。


          Chrome 調(diào)試 Nodejs

          上文講的是如何調(diào)試頁面,接下來我們聊如何調(diào)試 nodejs 應(yīng)用。首先來一個最容易上手的例子,創(chuàng)建一個 hello world:

          // debug.js 文件 
          const greeting = 'hello nodejs debugger' 
          debugger 
          console.log(greeting)

          然后運行這個文件

          node --inspect-brk debug.js 
          Debugger listening on ws://127.0.0.1:9229/b9a6d6bf-baaa-4ad5-8cc6-01eb69e99f0a 
          For help, see: https://nodejs.org/en/docs/inspector

          --inspect-brk 表示運行這個 js 文件的同時,在文件的第一行打上斷點。然后打開 Chrome,進入 Devtools。點擊紅框處的按鈕,就會打開一個 nodejs 專用的調(diào)試窗口,并且代碼在第一行中斷了。

          nodejs 調(diào)試窗口:

          這個方式的實質(zhì)是,Chrome Devtool 根據(jù) v8引擎的調(diào)試協(xié)議 向 nodejs 進程發(fā)送指令,控制代碼的運行。可以發(fā)現(xiàn),在網(wǎng)頁的調(diào)試中,Chrome 是接受指令的一方,而在 nodejs 調(diào)試中,Chrome 轉(zhuǎn)身變?yōu)榘l(fā)送指令的一方。所謂從悲慘的乙方華麗轉(zhuǎn)身成甲方。

          node 默認(rèn)的 websocket 端口是 9229,如果有需要的話(比如端口被占用了),我們可以通過一些方式改變這個端口。

          node --inspect=9228 debug.js 
          Debugger listening on ws://127.0.0.1:9228/30f21d45-9806-47b8-8a0b-5fb97cf8bb87 
          For help, see: https://nodejs.org/en/docs/inspector

          在我們打開 Devtool 時,Chrome 默認(rèn)檢查 9229 端口,但當(dāng)我們改變了端口號后,就需要手動去指定 Chrome 檢查的地址了。點擊下圖中的 Configure 按鈕,輸入 127.0.0.1:9228,然后點擊 Done。這時候 Remote Target 中就會出現(xiàn) 剛才啟動的 node 進程,點擊 inspect 就可以進入調(diào)試了。

          使用 VS Code 調(diào)試 Nodejs

          到此為止,我們已經(jīng)達成調(diào)試 node 的目的,但還有些繁瑣,不夠自動化。我們可以使用 VS Code,來一鍵啟動調(diào)試。

          用 VS Code 打開剛才的工程,然后在 launch.json 中輸入這些:

          { 
            "version": "0.2.0", 
            "configurations": [ 
              { 
                "type": "pwa-node", 
                "request": "launch", 
                "name": "Launch Program", 
                "skipFiles": [ 
                  "<node_internals>/**" 
                ], 
                // ${file} 的意思是,當(dāng)我們啟動調(diào)試的時候,調(diào)試的程序就是當(dāng)前 focus 的文件。 
                "program": "${file}" 
              } 
            ] 
          }

          這時候切換到 index.js 文件,按 f5 啟動調(diào)試程序,當(dāng)運行到第二行 debugger 語句的時候,就會自動暫停執(zhí)行。也可以點擊代碼行數(shù)的左側(cè)來打斷點。

          另外,這個配置是支持 TypeScript 的,我們只需要 index.js 重命名為 index.ts,然后正常啟動調(diào)試就行。


          Conditional Breakpoint 條件斷點

          在某些情況下,我們不希望打上的每個斷點都發(fā)揮作用,而是在執(zhí)行到斷點那行,且滿足某個條件再中斷代碼執(zhí)行。這就是條件斷點。

          for (let i = 0; i < 10; i++) { 
            console.log("i", i); 
          }

          比如上面的代碼,假設(shè)我們在第二行 console.log 打了斷點,那么這個斷點總計會中斷十次。這往往是我們不希望看到的,可能我們需要的僅僅是其中某一次循環(huán)而非所有。這時候可以右鍵點擊并選擇 Add Conditional Breakpoint。

          這時會有一個輸入框出現(xiàn),我們在其中輸入 i === 5。

          這時候啟動調(diào)試,就會跳過 i 為 0 - 4,直接在在 i 為 5 的時候中斷代碼執(zhí)行。恢復(fù)代碼執(zhí)行后,會略過 i 為 6 - 9 的情況。

          Conditional Breakpoint 在調(diào)試帶有大量循環(huán)和 if else 判斷時極為有用,特別是當(dāng)某處的邏輯整體上是符合預(yù)期的,僅有個別特殊情況的輸出錯誤,使用條件斷點就可以略過這些正常的情況,只在個別特殊情況出現(xiàn)的時候,再中斷執(zhí)行,供我們查看各個變量是否計算正常。


          總結(jié)

          調(diào)試是日常工作中非常重要的能力,因為除了開發(fā)新功能外,日常有很大一部分都在調(diào)整舊的代碼,處理特別條件下的邏輯錯誤。熟練掌握調(diào)試可以很好地提升搬磚幸福感,一個復(fù)雜的 bug 卡幾小時,很容易讓人心里崩潰。但也不是說斷點調(diào)試是任何情況下都適用的銀彈,簡單的邏輯還是可以愉快地 console.log 的。

          文章介紹了使用 Chrome Devtools 和 VS Code 斷點調(diào)試的方法,整體上還是更推薦使用 VS Code。launch.json 只需要一次配置,后續(xù)都可以 f5 一鍵啟動調(diào)試。另外,文中提到的各種 launch.json 文件的配置,都可以使用 VS Code 自帶的工具一鍵生成。只要打開 launch.json,編輯器的右下角就會出現(xiàn) Add Configuration 按鈕,點擊就可以選擇自己需要添加的調(diào)試配置。


          主站蜘蛛池模板: 无码人妻精品一区二区三区99仓本 | 日韩一区二区三区四区不卡| 日本一区二区三区在线视频观看免费| 在线播放精品一区二区啪视频| 深田咏美AV一区二区三区| 日韩免费无码视频一区二区三区 | 青娱乐国产官网极品一区| 性盈盈影院免费视频观看在线一区| 波多野结衣一区二区三区高清在线| 日本一区二区在线不卡| 久久久久久综合一区中文字幕 | 亚洲一区AV无码少妇电影| 久久精品人妻一区二区三区 | 一区二区三区国产精品| 亚洲国产精品乱码一区二区 | 久久亚洲日韩精品一区二区三区| 国产色综合一区二区三区| 中文字幕VA一区二区三区| 国产在线无码一区二区三区视频| 久久久精品一区二区三区| 精品一区精品二区制服| 国产高清一区二区三区| 日本一区二区三区在线观看视频| 波多野结衣中文字幕一区| 无码人妻AⅤ一区二区三区水密桃| 一区二区不卡久久精品| 国精产品一区一区三区免费视频| 国产综合一区二区| 极品人妻少妇一区二区三区| 精品一区二区三人妻视频| 影院无码人妻精品一区二区| 中文字幕一区二区三区有限公司| 国产suv精品一区二区33| 亚洲一区在线观看视频| 国产日韩精品一区二区在线观看播放 | 99久久人妻精品免费一区| 日韩一区二区三区四区不卡| 无码精品久久一区二区三区| 亚洲AV无一区二区三区久久| 2021国产精品视频一区| 国产AV一区二区精品凹凸|