Список неочевидных, но при этом полезных хаков, которые позволят использовать отладчик вашего браузера1 более полноценно. Для понимания материала статьи потребуется как минимум средний уровень владения инструментами разработчика.
▍ Содержание
- Продвинутые условные точки останова
- Точки логирования/трассировки
- Изменение поведения программы
- Быстрый и грубый способ профилирования производительности
- Использование арности функций
- Использование времени
- Использование CSS
- Только чётные вызовы
- Случайная остановка
- Никогда не останавливаться здесь
- Автоматическое присваивание ID экземплярам классов
- Программное переключение
- Отслеживание вызовов класса с помощью monitor()
- Вызов и отладка функции
- Остановка выполнения при изменении URL
- Отладка считывания свойств
- Использование copy()
- Отладка HTML/CSS
- Инспектирование DOM при отключённом JS
- Инспектирование исчезающего элемента
- Сохранение снимков DOM
- Мониторинг выбранного элемента
- Поиск элементов, выделенных жирным
- Ссылка на выбранный элемент
- Мониторинг связанных с элементом событий
Продвинутые условные точки останова
Используя выражения, которые оказывают побочные эффекты в неожиданных местах, мы можем выжать из базовых возможностей, вроде условных точек останова, дополнительную функциональность.
▍ Точки логирования/трассировки
К примеру, можно выполнять в точках останова команду
console.log
, превращая их в точки логирования, которые производят вывод в консоль без приостановки выполнения. Эта функциональность доступна во всех ведущих браузерах (Chrome Logpoints, Edge Tracepoints, Firefox Logpoints).Если вы хотите также подсчитывать, сколько раз выполняется строка, то вместо
console.log
используйтеconsole.count
.▍ Панель наблюдения
Также можно использовать
console.log
в панели наблюдения. Например, чтобы сохранять снимокlocalStorage
при каждой остановке приложения в отладчике, создайте в панели наблюдения элементconsole.table(localStorage)
:Или же, чтобы выполнять выражение после изменения DOM, установите соответствующую точку останова в инспекторе элементов:
Затем добавьте, например, это выражение для записи снимка DOM:
(window.doms = window.doms || []).push(document.documentElement.outerHTML)
. Теперь после любого изменения поддерева DOM отладчик будет останавливать выполнение, и в конце массиваwindow.doms
будет создаваться новый снимок DOM. (Невозможно создать такую точку останова при изменении DOM, которая бы не останавливала выполнение).▍ Трассировка стеков вызовов
Предположим, у вас есть функция, показывающая спиннер загрузки, и другая функция, его скрывающая. Но где-то в коде вы вызываете первую без соответствующей второй. Как обнаружить источник этого непарного вызова? Используйте
console.trace
в условной точке останова, расположенной в методе показа спиннера, выполните код, найдите последнюю трассировку стека вызовов этого метода и кликните по вызывающему компоненту для перехода к его коду:▍ Изменение поведения программы
Используя выражения, оказывающие побочные эффекты на поведение программы, можно изменять это поведение «на лету» прямо в браузере.
Например, вы можете переопределить параметр
id
функцииgetPerson
. Посколькуid=1
будет оценено какtrue
, эта условная точка будет останавливать отладчик. Чтобы это предотвратить, добавьте в выражениеfalse
.▍ Быстрый и грубый способ профилирования быстродействия
Не стоит загрязнять профилирование производительности такими вещами, как время вычисления условных точек останова, но если вы хотите быстро и грубо измерить продолжительность выполнения чего-либо, то можете использовать в этих точках доступное в консоли API тайминга.
В качестве начальной установите точку останова с условием
console.time('label')
, а в качестве завершающей — с условиемconsole.timeEnd('label')
. Теперь при каждом выполнении указанного процесса браузер будет выводить в консоль продолжительность этого выполнения.▍ Использование арности функций
▍ Остановка на основе указанного количества аргументов
Приостановка, только когда текущая функция вызывается с 3 аргументами:
arguments.callee.length === 3
Такой приём полезен, когда у вас есть перегруженная функция, содержащая необязательные параметры.
▍ Остановка на основе несоответствия указанному числу аргументов
Приостановка, только когда текущая функция вызывается с несоответствующим числом аргументов:
(arguments.callee.length) != arguments.length
Пригождается при поиске багов в точках вызова функции.
▍ Использование времени
▍ Пропуск загрузки страницы
Не останавливать выполнение, пока не пройдёт 5 секунд после загрузки страницы:
performance.now() > 5000
Пригождается, когда вам нужно установить точку останова, которая должна сработать только после первичной загрузки страницы.
▍ Пропуск N секунд
Не останавливать выполнение, если точка останова встречается в течение 5 следующих секунд:
window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000
Сбрасывайте счётчик из консоли в любой нужный вам момент:
window.baseline = Date.now()
▍ Использование CSS
Производите остановку на основе вычисленных значений CSS, например, только когда в теле документа используется фон красного цвета:
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
▍ Только чётные вызовы
Остановка только при каждом втором выполнении строки:
window.counter = (window.counter || 0) + 1, window.counter % 2 === 0
▍ Случайная остановка
Можно производить остановку случайно на основе заданной частоты, например, останавливать только 1 из каждых 10 выполнений:
Math.random() < 0.1
▍ Никогда не останавливаться здесь (Chrome)
Если кликнуть правой кнопкой в поле gutter и выбрать «Never Pause Here», Chrome создаст условную точку останова, которая всегда будет
false
, и отладчик на этой строке никогда останавливаться не будет.Пригождается, когда вам нужно исключить остановку в определённой строке XHR (XmlHttpRequest), проигнорировать исключение и так далее.
▍ Автоматическое присваивание ID экземплярам классов
Можно автоматически присваивать уникальный идентификатор каждому экземпляру класса, определив в конструкторе такую условную точку останова:
(window.instances = window.instances || []).push(this)
Затем для извлечения этого уникального ID нужно выполнить
window.instances.indexOf(instance)
(например,window.instances.indexOf(this)
, находясь в методе класса).▍ Программное переключение
Используйте глобальное логическое значение для активации одной или более условных точек останова:
Затем программно переключайте это логическое значение, например:
- Вручную из консоли:
window.enableBreakpoints = true;
- Из других точек останова:
- Из таймера через консоль:
setTimeout(() => (window.enableBreakpoints = true), 5000);
- И так далее.
Отслеживание вызовов класса с помощью monitor() (Chrome)
В Chrome есть удобный метод командной строки
monitor
, позволяющий легко отслеживать все вызовы методов класса. Вот пример с классомDog
:class Dog { bark(count) { /* ... */ } }
Если нам нужно знать все вызовы, совершаемые ко всем экземплярам
Dog
, то мы выполняем из командной строки следующее:var p = Dog.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
И получаем в консоли такой вывод:
> function bark called with arguments: 2
Также, если вам нужно останавливать выполнение на определённых вызовах метода, а не просто выводить результат в консоль, то вместо
monitor
используйтеdebug
.▍ Отслеживание вызовов из конкретного экземпляра (Chrome)
Если вы не знаете класс, но у вас есть его экземпляр:
var p = instance.constructor.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
Пригождается, когда вы хотите занести в журнал функцию, которая проделывает это для любого класса (а не только для
Dog
).▍ Вызов и отладка функции
Прежде чем вызывать в консоли функцию для отладки, вызовите
debugger
. Например, если дано:function fn() { /* ... */ }
Выполните из консоли:
> debugger; fn(1);
И затем «Step into the next function call» для отладки реализации
fn
.Пригождается, когда вы не можете найти определение
fn
и добавить точку останова вручную, или еслиfn
динамически привязана к функции, и вы не знаете, где находится её источник.В Chrome также можно при желании выполнить из командной строки
debug(fn)
, и отладчик будет останавливать выполнение внутриfn
при каждом её вызове.▍ Остановка выполнения при изменении URL
Чтобы остановить выполнение до того, как одностраничное приложение изменит URL (то есть до любого события маршрутизации):
const dbg = () => { debugger; }; history.pushState = dbg; history.replaceState = dbg; window.onhashchange = dbg; window.onpopstate = dbg;
Ну а создание версии
dbg
, которая останавливает выполнение, не нарушая навигацию, останется для читателей в качестве упражнения.Также заметьте, что вызовы кода
window.location.replace/assign
здесь не обрабатываются напрямую, поскольку страница после присваивания сразу выгружается, и отлаживать уже нечего.Если же вы всё равно хотите увидеть исходный код этих перенаправлений (и отладить состояние в момент этого перенаправления), то в Chrome можете применить
debug
к сопутствующим методам:debug(window.location.replace); debug(window.location.assign);
▍ Отладка считывания свойств
Если у вас есть объект, и вы хотите знать, когда в нём считывается свойство, используйте геттер объектов вместе с вызовом
debugger
. Например, переделайте{configOption: true}
в{get configOption() { debugger; return true; }}
(в самом исходном коде или с помощью условной точки останова).Пригождается, когда вы передаёте куда-то настройки конфигурации и хотите видеть, как они используются.
▍ Использование copy() (Chrome, Firefox)
Вы можете копировать интересующую вас информацию из браузера напрямую в буфер обмена без обрезания строк, используя API
copy()
. В качестве такой информации может выступать:- Снимок текущей DOM:
copy(document.documentElement.outerHTML)
. - Метаданные ресурсов (например, изображений):
copy(performance.getEntriesByType("resource"))
. - Большой отформатированный blob-объект JSON:
copy(JSON.parse(blob))
. - Дамп
localStorage
:copy(localStorage)
. - И так далее.
Отладка HTML/CSS
При диагностировании проблем с HTML/CSS кодом может пригодиться консоль JS.
▍ Инспектирование DOM при отключённом JS
Находясь в инспекторе DOM, в любой момент нажмите Ctrl+ (Chrome/Windows), чтобы приостановить выполнение JS-кода. Это позволит вам просмотреть снимок DOM, не беспокоясь о том, что его изменит JS или какие-то события (например, наведение курсора мыши).
▍ Инспектирование исчезающего элемента
Предположим, вы хотите просмотреть элемент DOM, который появляется только при определённых условиях. Для инспектирования такого элемента на него необходимо навести курсор мыши, но когда вы это делаете, он исчезает:
Чтобы просмотреть его, выполните в консоли:
setTimeout(function() { debugger; }, 5000);
. Это даст вам 5 секунд для активации UI, затем по прошествии этих 5 секунд выполнение JS приостановится, и уже ничто не заставит нужный элемент исчезнуть. Теперь вы можете навести курсор на инструменты разработки, не потеряв его из виду:В момент приостановки выполнения JS вы можете инспектировать элемент, редактировать его CSS-код, выполнять команды в консоли JS и так далее.
Этот приём пригождается при инспектировании DOM, которая зависит от конкретного положения курсора, выбранного элемента и так далее.
▍ Сохранение снимков DOM
Чтобы сделать копию DOM в её текущем состоянии, используйте:
copy(document.documentElement.outerHTML);
Также можете делать снимок DOM каждую секунду:
doms = []; setInterval(() => { const domStr = document.documentElement.outerHTML; doms.push(domStr); }, 1000);
Или просто выводить снимок в консоль:
setInterval(() => { const domStr = document.documentElement.outerHTML; console.log("snapshotting DOM: ", domStr); }, 1000);
▍ Мониторинг выбранного элемента
(function () { let last = document.activeElement; setInterval(() => { if (document.activeElement !== last) { last = document.activeElement; console.log("Focus changed to: ", last); } }, 100); })();
▍ Поиск элементов, выделенных жирным
const isBold = (e) => { let w = window.getComputedStyle(e).fontWeight; return w === "bold" || w === "700"; }; Array.from(document.querySelectorAll("*")).filter(isBold);
▍ Отслеживание только потомков
Можно мониторить только потомков элемента, выбранного в инспекторе:
Array.from($0.querySelectorAll("*")).filter(isBold);
▍ Ссылка на выбранный элемент
Использование
$0
в консоли станет автоматической ссылкой на выбранный в инспекторе элемент.▍ Ссылка на предыдущие элементы (Chrome, Edge)
В Chrome и Edge можно обращаться к последнему проинспектированному элементу с помощью
$1
, к элементу перед ним с помощью$2
и так далее.▍ Получение слушателей событий (Chrome)
В Chrome можно инспектировать слушателей событий выбранного элемента с помощью
getEventListeners($0)
. Например, так:▍ Мониторинг связанных с элементом событий (Chrome)
Отладка всех событий, связанных с выбранным элементом:
monitorEvents($0)
Отладка конкретных событий, связанных с выбранным элементом:monitorEvents($0, ["control", "key"])
▍ Сноска
1. Если для приёма не указаны поддерживающие его браузеры, то он по умолчанию работает в Chrome, Firefox и Edge. ↩
Автор: Bright_Translate