ブラウザ上でデバッグするときに使えるテクニック
ウェブ上でJavaScriptを実行してバグが発生した場合、ブラウザに内蔵されている開発者ツールを使ってデバッグすることがよくあります。そうしたブラウザでのデバッグにおいて役立つテクニックをNetflixでフロントエンドの開発に携わっているアラン・ノルバウアーさんがまとめています。
67 Weird Debugging Tricks Your Browser Doesn't Want You to Know | Alan Norbauer
https://alan.norbauer.com/articles/browser-debugging-tricks
◆高度な条件付きブレークポイント
開発者ツールの「ソース」タブにはデバッガーが用意されており、JavaScriptの任意の行にブレークポイントを設定することで実行を一時停止して変数やコールスタックの中身を確認できます。ブレークポイントを設定する際に右クリックすると下図のようなメニューが出現し、通常のブレークポイントのほか条件付きブレークポイントやログポイントを追加できます。こうした機能の活用方法は下記の通り。
・ログポイント・トレースポイント
ログポイントは中に設定した変数やテキストを一時停止することなくコンソールに出力する機能で、多くのブラウザの開発者ツールに標準搭載されています。また、ログポイントがサポートされていないブラウザの場合でも条件付きブレークポイントを使用することでログポイントと同様の動作をさせることが可能です。
・監視パネル
「監視」パネルにconsole.logを設定することができ、デバッガーで一時停止が発生するたびに特定の変数の中身を出力させることも可能です。
DOMに変更があった時に一時停止したい場合は要素パネルから設定可能です。
そして下記のようなコードを利用することでDOMの状態を保存できます。
(window.doms = window.doms || []).push(document.documentElement.outerHTML)
・コールスタックのトレース
例えばロードスピナーを表示する関数と非表示にする関数があり、うっかりどこかで2回表示させてしまっている時など、どこから呼び出されているのかを調査したい場合があります。その場合はログポイントに「console.trace」を設定すればOK。
ログポイントを通過するたびにコンソールに呼び出し元が表示され、どこから呼び出されたのかが1発で分かります。
・プログラムの動作を変更する
条件付きブレークポイントに副作用が発生する式を入力することでプログラムの動作を変更できます。中身が「true」と評価される式の場合、そこでプログラムが一時停止してしまうため、停止させたくない場合は最後に「, false」とつなげればOK。
・迅速なパフォーマンス計測
計測したい範囲の最初に「console.time('label')」を設定し、計測したい範囲の最後に「console.timeEnd('label')」と設定することで簡単にその部分の実行にかかった時間を計測できます。
・引数の数次第でブレークする
「arguments」を使用すると引数が特定の数だった場合のみ一時停止させることが可能です。
また、「arguments.callee」と比較することで引数の数が不正だった場合のみ一時停止させることもできます。
・時間を条件にする
「performance.now()」を使用することでページを開いてから特定の時間が経過後のみ一時停止する設定ができます。例えば「performance.now() > 5000」とすればページが開いてから5秒間はそのブレークポイントでは一時停止しません。
また、下記のように設定すれば一度そのブレークポイントを通過してから5秒間は同じブレークポイントで一時停止しないようにできます。
window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000
・CSSを使う
計算後のCSSの値に基づいて一時停止させることが可能です。例えば下記のコードであればbodyの背景色が赤色だった場合のみ一時停止します。
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
・特定の回数時のみ一時停止する
グローバル変数でカウントすればOK。
window.counter = window.counter || 0, window.counter % 2 === 0
・サンプリング
「Math.random()」を使うことで一定確率でのみ一時停止させることが可能です。
Math.random() < 0.1
・一時停止しない
「ここで一時停止しない」を設定することでその行で一時停止が発生しないようにできます。特定のタイプのAjaxで一時停止するXHRブレークポイントを無効化したり、例外を無視したりする場合に便利です。
・自動でインスタンスを収集
コンストラクタに下記の条件付きブレークポイントを仕込むことで生成されたインスタンスを収集できます。
(window.instances = window.instances || []).push(this)
・複数のブレークポイントを一括管理
Bool型のグローバル変数を使用して複数のブレークポイントのオンオフを一括管理できます。オンオフを切り替えるにはコンソールから手動で設定したり、他の条件付きブレークポイントで設定したりすればOK。
◆monitorの活用
Chromeには「monitor()」コマンドが内蔵されています。monitorコマンドに関数を渡すことで、その関数が呼び出された際にコンソールへ出力が行われるようになります。
例えば「Dog」というクラスの全てのインスタンスに対する呼び出しを知りたい場合、下記のようにコンソールに入力すればOK。
Dogの「bark」メソッドが呼び出された場合、コンソールに下記のように出力されます。なお、出力時に一時停止したい場合は「monitor」の代わりに「debug」を使用します。
function bark called with arguments: 2
また、インスタンスは見つかっているもののクラスが分からない場合は下記のコードで同じ動作を実現できます。
◆関数の呼び出し
例えば「fn()」という関数をデバッグしたい場合、コンソールで呼び出す前に下記のように「debugger」を呼び出せば最初からステップ実行することが可能です。ブレークポイントと違い、関数のソースがどこにあるのか分からない場合でも実行できます。
debugger; fn(1);
なお、Chromeの場合は「debug(fn)」とコンソールに入力するとfn関数の呼び出し時に自動で一時停止し、ステップ実行に入ることが可能です。
◆URLの変更時に一時停止
シングルページアプリケーションがURLを変更する前に一時停止するには下記のコードをコンソールに入力すればOK。
なお、アプリが「window.location.replace/assign」を直接呼び出している場合には直後にページがアンロードされるため上記のコードが機能しません。ページがアンロードされることでデバッグする対象がなくなってしまいますが、それでも一時停止したい場合は両方の関数をChromeのdebugに登録します。
◆プロパティの読み取りをデバッグする
オブジェクトのプロパティが読み取られた時に一時停止したい場合はオブジェクトにゲッターを追加し、元の値を返す前に「debbuger」を設定します。
◆copy()を使用する
コンソールAPIの「copy()」を使用すると文字列を切り詰めることなく情報を直接クリップボードに取り込むことができます。下記のような活用例が挙げられています。
現在のDOMのスナップショット
copy(document.documentElement.outerHTML)
リソースに関するメタデータ
copy(performance.getEntriesByType("resource"))
大きなJSON形式のBlob
copy(JSON.parse(blob))
localStorageのダンプ
copy(localStorage)
◆HTMLやCSSのデバッグ
・JavaScriptを停止する
ソースパネルで「Ctrl+\」もしくはF8キーを押すことでJavaScriptを一時停止できます。JavaScriptを停止しておくことで、JavaScriptにDOMを変更される心配なく要素を確認することが可能です。
・特定の状態時のみ出現する要素を検査する
例えば下図のように、「first input」にカーソルが載っているときだけ「second input」が出現するページがある場合を考えます。
要素を検査するには検査したい要素にカーソルを合わせる必要があり、このままでは「second input」を検査できません。
一定時間後にコードを実行する「setTimeout()」とdebuggerを組み合わせる事で、「first input」にカーソルをあわせた状態でJavaScriptの実行を停止することができます。これでsecond inputを検査できるようになりました。
・DOMのスナップショットを記録する
copyの活用の項目でも述べた通り、下記のコマンドでDOM全体をクリップボードにコピーできます。
下記のようにすればDOMのスナップショットを毎秒取得して保存可能。
・フォーカスをモニターする
下記のようなコードを用いてactiveElementの切り替えを検出できます。
フォーカスが切り替わるたびにコンソールに要素を出力できます。
・CSSに基づいて検索する
「document.querySelectorAll("*")」を使うと全ての要素を取得できることと「window.getComputedStyle()」を利用して、下記のコードのように特定のスタイルを持つ要素だけを抽出することができます。
「document」の代わりに「$0」を用いれば現在インスペクターで指定している要素の子孫のみを検索可能です。
・選択中の要素を取得
コンソールで「$0」を用いることでインスペクターで選択中の要素を指定できます。ChromeおよびEdgeにおいては、「$1」や「$2」を用いるとそれぞれ1つ前に選択した要素、2つ前に選択した要素を指定することも可能です。
・イベントリスナーを取得
Chromeでは「getEventListeners()」を使用すると要素に設定しているイベントリスナーを取得できます。
・イベントを監視する
Chromeでは「monitorEvent($0)」とコマンドを入力することでその要素で発生したイベントをキャプチャすることができます。「monitorEvent($0, ["control", "key"])」のように指定することで特定のイベントのみをキャプチャすることも可能です。
◆フォーラム開設中
本記事に関連するフォーラムをGIGAZINE公式Discordサーバーに設置しました。誰でも自由に書き込めるので、どしどしコメントしてください!Discordアカウントを持っていない場合は、アカウント作成手順解説記事を参考にアカウントを作成してみてください!
• Discord | "どのブラウザのデバッグツールをメインに使ってる?" | GIGAZINE(ギガジン)
https://discord.com/channels/1037961069903216680/1176083336695918672
・関連記事
「JavaScript」はここから始まった、1995年のJavaScriptリリースはこんな感じ - GIGAZINE
JavaScriptのアンチデバッグ技術をシンプルな発想で回避する方法 - GIGAZINE
JavaScriptのイベントの仕組みが一発で理解できるウェブアプリ「Explore DOM Events」レビュー - GIGAZINE
ジェイムズ・ウェッブ宇宙望遠鏡はJavaScriptで制御されている - GIGAZINE
「プリンス・オブ・ペルシャ」をJavaScriptで構築しブラウザで遊べるようにした「PrinceJS」レビュー - GIGAZINE
・関連コンテンツ