原文:Pause Your Code With Breakpoints
做者:Kayce Basques Chrome DevTools & Lighthouse技術做家javascript
參考這份指南,結合本身手上的vue項目進行實踐,能夠說對原指南進行了plus,由於實踐過程當中會有不少指南以外的新發現。html
主要內容包括以下:前端
代碼行級(Line-of-code)斷點vue
DOM變化級斷點java
Function 斷點node
使用breakpoints去爲咱們的JavaScript代碼打斷點。這個指南涉及了在DevTools上適用的每一種breakpoint類型,而且會講解如何使用並設置每種類型的斷點。若是是想學習如何在Chrome DevTools上調試代碼,能夠看Get Started with Debugging JavaScript in Chrome DevToolsios
衆人皆知的breakpoint類型是line-of-code。可是line-of-code型breakpoint有的時候無法設置(其實就是無法在代碼左邊點出一個綠點來),或者若是你正在使用一個大型的代碼庫。經過學習如何和什麼時候使用這些不一樣類型的breakpoint debug,會大大節約你的時間。git
斷點類型 | 當你想Pause的時候使用 |
---|---|
Line-of-code | 代碼具體某一行(其實就是無法在代碼左邊點出一個綠點來) |
Conditional line-of-code | 代碼具體某一行,可是隻有在一些條件爲true時 |
DOM | 在改變或者移除一個DOM節點或者它的DOM子節點時 |
XHR | 當一個XHR URL包含一個string pattern |
Event Listener | 在運行了某個特定事件後的代碼上,例如click事件觸發 |
Exception | 在拋出了一個caught或者uncaught的exception時 |
Function | 當一個函數被調用時 |
若是你知道本身想在哪一具體的代碼行檢查代碼,會用到line-of-code breakpoint。DevTools會在這行代碼執行前暫停住。github
爲了在DevTools某一行設置斷點:web
下面的圖是我本地調試的圖,只有部分有定義,解構,回調,return等有須要process的地方,才能夠打斷點。
調用debugger
在那一行暫停。debugger等價於一個line-of-code斷點,這可讓端點直接出如今代碼中,而不是在DevTools UI中,能夠在任何機器上進行debug。
console.log('a'); console.log('b'); debugger; console.log('c');
在咱們知道本身須要檢查的一個特定代碼行時,使用有條件的行級斷點,可是隻有在一些其餘知足時才能夠暫停。
如何設置一個有條件的行級斷點:
Add breakPoint是普通的行級斷點,Add conditional breakPoint是有條件的行級斷點。Never Pause Here會讓這裏永遠不進斷點。Blackbox Script可讓當前打開的文件黑盒化,用不進入斷點。
點擊Add conditional breakPoint後,會出現dialog:
點擊Blackbox Script後,文件頭部會出現The script is blackboxed in debugger
:
在個人代碼中,添加了mediaType==='text'
的條件,而mediaType還會有'img','mini'等類型,可是經過添加conditional breakpoint,我能夠只在類型是text的時候pause,親測有效。
管理line-of-code斷點
能夠對單個斷點啓用,禁用,移除;對全部斷點啓用,禁用,移除;對除當前斷點外的斷點啓用,禁用,移除。
在改變或者移除一個DOM節點或者它的DOM子節點時會用到DOM change breakpoint。
圖5:建立一個DOM change breakpoint的上下文菜單
設置DOM級斷點的步驟以下:
我會在這樣一段代碼上測試Subtree modifications,Attribute modifications或者Node removal幾個DOM型斷點。
<div class="url-cover-container"> <cUpload accept="image/*" v-model="moment.params.url.cover" @finish="urlCoverUpload" v-if="urlCoverUploadShow"> <Button type="info">上傳封面</Button> </cUpload> <img v-else :src="moment.params.url.cover"/> </div>
渲染結果以下:
<div data-v-6eb44e66="" class="c-upload」> <input type="file" accept="image/*" style="display: none;」> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info」> <!——> // iview內部組件v-if else <!——> <span>上傳封面</span> </button> <!——> // v-if爲false,所以只保留一個註釋結束符 </div>
當我點擊上傳按鈕後,一次完整的上傳文件會引發5次DOM節點的變化:
<div data-v-6eb44e66="" class="url-cover-container"> <div data-v-6eb44e66="" class="c-upload"> <input type="file" accept="image/*" style="display: none;"> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info"> <!----> <!----> <span>上傳封面</span> </button> <!----> </div> </div>
<div data-v-6eb44e66="" class="url-cover-container"> <div data-v-6eb44e66="" class="c-upload"> <input type="file" accept="image/*" style="display: none;"> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info"> <!----> <!----> <span>上傳封面</span> </button> // 下面的div.c-upload-loading-container就是div.c-upload新增的Descendant <div class="c-upload-loading-container"> <div class="c-upload-loading"> <svg data-v-65d9369a="" viewBox="25 25 50 50" class="circular"> <circle data-v-65d9369a="" cx="50" cy="50" r="20" stroke-dasharray="125.66370614359172" stroke-dashoffset="0" class="path"></circle> </svg> </div> </div> <!----> </div> </div>
<div data-v-6eb44e66="" class="url-cover-container"> <div data-v-6eb44e66="" class="c-upload"> <input type="file" accept="image/*" style="display: none;"> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info"> <!----> <!----> <span>上傳封面</span> </button> <div class="c-upload-loading-container"> <div class="c-upload-loading"> <svg data-v-65d9369a="" viewBox="25 25 50 50" class="circular"> <circle data-v-65d9369a="" cx="50" cy="50" r="20" stroke-dasharray="125.66370614359172" stroke-dashoffset="-125.66370614359172" class="path"></circle> </svg> </div> </div> // 這裏的<!-- --> 註釋DOM子Descendant節點被移除 </div> </div>
<div data-v-6eb44e66="" class="url-cover-container"> // 下面的img就是div.url-cover-container新增的Child <img data-v-6eb44e66="" src="http://img.test.weidiango.com/FsHXLY5bu94-s6323xLHFSwh8GGM"> <div data-v-6eb44e66="" class="c-upload"> <input type="file" accept="image/*" style="display: none;"> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info"> <!----> <!----> <span>上傳封面</span> </button> <div class="c-upload-loading-container"> <div class="c-upload-loading"> <svg data-v-65d9369a="" viewBox="25 25 50 50" class="circular"> <circle data-v-65d9369a="" cx="50" cy="50" r="20" stroke-dasharray="125.66370614359172" stroke-dashoffset="-125.66370614359172" class="path"></circle> </svg> </div> </div> </div> </div>
<div data-v-6eb44e66="" class="url-cover-container"> // 這裏的div.c-upload就是被移除的Descendant <img data-v-6eb44e66="" src="http://img.test.weidiango.com/FsHXLY5bu94-s6323xLHFSwh8GGM"> </div>
child和descendant區別是什麼?
child僅僅包含一個子節點;descendant包含多個後代節點,會有一個孫子節點的狀況,例如上面移除的#comment類型的註釋子節點。
在3個狀態間切換,會觸發class屬性值的變化,由於須要使激活的按鈕高亮。
<div data-v-6eb44e66="" name="ivuRadioGroup_1538124450566_3" class="ivu-radio-group ivu-radio-group-default ivu-radio-default ivu-radio-group-button"> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-default"> 圖文 </label> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-default"> 小視頻 </label> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-wrapper-checked ivu-radio-default ivu-radio-focus"> 圖文連接 </label> </div>
當狀態從圖文連接切換到小視頻時,會進入Attribute Modifications斷點,:
<div data-v-6eb44e66="" name="ivuRadioGroup_1538124450566_3" class="ivu-radio-group ivu-radio-group-default ivu-radio-default ivu-radio-group-button"> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-default"> 圖文 </label> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-wrapper-checked ivu-radio-default ivu-radio-focus"> 小視頻 </label> <label data-v-6eb44e66="" class="ivu-radio-wrapper ivu-radio-group-item ivu-radio-wrapper-checked ivu-radio-default"> 圖文連接 </label> </div>
經過此次DOM Attribute Modification斷點,咱們發現,iview的單選ButtonGroup高亮依賴.ivu-radio-focus,而且會爲以前選擇過的radio添加.ivu-raido-wrapper-checked。
由於咱們只對圖文連接label設置了Attribute Modification斷點,因此小視頻label的屬性變化不會被檢測到:
移除一個DOM節點時會進入斷點。
所以,其實就是subtree modifications中的最後一次DOM變化,把div.c-upload徹底移除,只顯示一個上傳後的圖片。可是可能與subtree modifications的斷點提示不同,所以我將從新記錄斷點。
<div data-v-6eb44e66="" class="url-cover-container"> // 下面的img就是div.url-cover-container新增的Child <img data-v-6eb44e66="" src="http://img.test.weidiango.com/FsHXLY5bu94-s6323xLHFSwh8GGM"> <div data-v-6eb44e66="" class="c-upload"> <input type="file" accept="image/*" style="display: none;"> <button data-v-6eb44e66="" type="button" class="ivu-btn ivu-btn-info"> <!----> <!----> <span>上傳封面</span> </button> <div class="c-upload-loading-container"> <div class="c-upload-loading"> <svg data-v-65d9369a="" viewBox="25 25 50 50" class="circular"> <circle data-v-65d9369a="" cx="50" cy="50" r="20" stroke-dasharray="125.66370614359172" stroke-dashoffset="-125.66370614359172" class="path"></circle> </svg> </div> </div> </div> </div>
<div data-v-6eb44e66="" class="url-cover-container"> // 這裏的div.c-upload就是被移除的Descendant <img data-v-6eb44e66="" src="http://img.test.weidiango.com/FsHXLY5bu94-s6323xLHFSwh8GGM"> </div>
確實與以前的不一樣,直接提醒當前node被移除,而不是做爲Child或者Descendant被移除。
XHR的請求URL包含指定字符串時,使用XHR中斷。當XHR調用send()方法時在對應行暫停。
注意:這個特性一樣做用於Fetch請求。
在XHR/Fetch上打斷點的場景,很是適用於請求一個不正確的URL,並且你想很快找到AJAX或者Fetch 源代碼去發現致使錯誤請求的緣由。
設置一個XHR類型的斷點的步驟以下:
在XHR Breakpoints中爲包含URL中的org的任何請求建立XHR斷點
實驗:
當前頁有5個來自crm.test.foo.com的請求,每次都會進入到XHR斷點。
發出請求前進行斷點:
會進入到xhr.js中,開心地debug:
若是要暫停事件觸發後運行的事件偵聽器代碼,請使用事件偵聽器斷點。咱們能夠選擇特定的事件,例如click事件;或者事件類別,例如全部的鼠標事件。
圖7:爲deviceorientation新建一個事件監聽斷點
有不少類型的Event Listener。
實驗:
會進入到第三方庫監聽事件的代碼,能夠用Blackbox Script跳過進入這個文件的斷點,直接進入到咱們的監聽click事件的業務代碼的地方,方便debug。結合CallStack和Scope,能夠提高咱們的debug效率。
若是想深刻學習第三庫的源碼,能夠打開斷點,每次都去看一次事件發生,第三方庫到底作了什麼。
當咱們想在某一行拋出caught or uncaught異常時進入斷點,可使用exception breakpoint。
。變藍了就表示開啓了,此時默認有未捕獲的異常時進入斷點。
圖7:在一個未捕獲的異常處暫停
捕獲到一個CORS異常:
經過異常捕獲斷點,咱們能夠進入到axios,以及二次封裝的httpclient中,去看Exception是如何被處理的,出現問題時,能夠精肯定位。
有些時候可能不是咱們業務代碼的bug,是第三方庫的bug,經過這樣的斷點,咱們能夠在業務代碼沒有問題的狀況下,深刻到第三方庫找問題(雖然通常都是本身業務代碼的問題)。
親測:默認捕獲的異常時uncaught類型,開啓Pause on caught exceptions,會讓uncaught和caught類型的均進入斷點。
假設咱們想對某一個函數作debug的話,例如調用debug(functionNmae)
,functionName是咱們想debug的函數。你能夠在你的代碼中像插入console.log()
同樣,插入debug()
,或這在控制檯裏直接輸入debug()
。debug()至關於在某個函數的第一行設置了一個line-of-code breakpoint。
function sum(a, b) { let result = a + b; // DevTools pauses on this line. return result; } debug(sum); // Pass the function object, not a string. sum();
能夠用來確保目標函數位於scope中
若是咱們想要debug的函數不在當前做用域中,DevTools會拋出一個ReferenceError
。
(function () { function hey() { console.log('hey'); } function yo() { console.log('yo'); } debug(yo); // This works. yo(); })(); debug(hey); // This doesn't work. hey() is out of scope.
若是從DevTools控制檯調用debug(),確保目標函數在範圍內可能會很棘手,有一個辦法:
debug()
嗯,上面這個方法很雞肋,親測。
實驗:
function foo(){ function bar() { console.log('bar'); } debug(bar); bar(); } foo(); // 進入斷點 debug(bar); // 拋出Uncaught ReferenceError: bar is not defined
期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:
- SegmentFault技術圈:ES新規範語法糖
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
- 知乎專欄:趁你還年輕,作個優秀的前端工程師
- Github博客: 趁你還年輕233的我的博客
- 前端開發QQ羣:660634678
- 微信公衆號: 人獸鬼 / excellent_developers
努力成爲優秀前端工程師!