Отладка в браузере
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Код можно перечитывать до бесконечности в надежде на озарение — а можно один раз заглянуть внутрь работающей программы и увидеть настоящие значения переменных. Этим и занимается отладка: её главные инструменты — console.log и встроенный в браузер отладчик.
Самый доступный инструмент отладки уже встроен в ваш код — это console.log. Он печатает любое значение в консоль браузера (она открывается клавишей F12, а на Mac — Cmd+Option+J).
Печатать наугад малополезно. У опытных разработчиков есть стратегия: логировать входы и выходы функции и помечать каждый вывод меткой — иначе в потоке одинаковых цифр быстро запутаешься. Вот функция скидки, которая почему-то возвращает NaN:
Метка «входы» сразу выдаёт виновника: percent пришёл как undefined, потому что функцию вызвали с одним аргументом вместо двух. Формула ни при чём — и мы узнали это за один запуск, не перечитывая код.
Методы консоли целиком разобраны в статье «Консоль браузера: первый инструмент отладки»; при охоте за багом от них нужно одно — заметность. Красная строка console.error и жёлтая console.warn находятся в длинном логе глазами, а console.table показывает сразу весь массив, не заставляя раскрывать объекты по одному.
console.log показывает отдельные снимки, а иногда хочется поставить фильм на паузу и осмотреться. Для этого есть оператор debugger: если инструменты разработчика открыты, выполнение замрёт на этой строке — со всеми переменными, доступными для изучения.
Если DevTools закрыт, строка тихо игнорируется, поэтому случайно «сломать» страницу пользователю debugger не может. Но перед публикацией кода его всё равно принято убирать — как строительные леса после ремонта.
Оператор debugger требует править код. Панель Sources в DevTools умеет то же самое без правок: откройте вкладку Sources, найдите свой файл и кликните по номеру нужной строки — на нём появится синяя метка, брейкпоинт. Как только выполнение дойдёт до этой строки, программа встанет на паузу.
Во время паузы справа появляется панель Scope — все переменные, видимые в этой точке: локальные, из замыкания, глобальные. А если навести курсор на имя переменной прямо в коде, её текущее значение всплывёт подсказкой. Дальше выполнением управляют кнопки:
| Кнопка | Клавиша | Что делает |
|---|---|---|
| Resume | F8 | Продолжить выполнение до следующего брейкпоинта |
| Step over | F10 | Выполнить текущую строку целиком, не заходя внутрь вызовов |
| Step into | F11 | Зайти внутрь функции, вызываемой на текущей строке |
| Step out | Shift+F11 | Доработать текущую функцию и выйти к месту вызова |
Over — мимо, Into — внутрь
Если цикл ломается на сотой итерации, останавливаться сто раз подряд мучительно. Кликните по номеру строки правой кнопкой и выберите «Add conditional breakpoint» — пауза сработает, только когда условие истинно, например i === 100.
В Scope показывается всё подряд, а вас часто интересует одно конкретное значение. Добавьте его во вкладку Watch: нажмите «+» и впишите любое выражение — items.length, price * qty, что угодно на JavaScript.
DevTools будет пересчитывать это выражение после каждого шага. Так удобно ловить момент, где значение неожиданно меняется: вы шагаете по строкам и смотрите на одну цифру, а не сканируете глазами весь Scope.
Любой поиск бага сводится к трём шагам. Сначала — воспроизвести: найти действия, после которых ошибка повторяется каждый раз; плавающий баг ловить бесполезно. Затем — сузить: логами или брейкпоинтами найти строку, где данные из правильных превращаются в неправильные. И наконец — проверить гипотезу: предположить причину и подтвердить её экспериментом. Посмотрим на живом примере — функция должна считать среднее, но возвращает NaN:
Ошибка воспроизводится стабильно — первый шаг готов. Сужаем: раз итог NaN, в сумму попало что-то нечисловое. Логируем каждый шаг цикла:
Вот и гипотеза, она же разгадка: условие i <= nums.length делает один лишний шаг, nums[3] — это undefined, а число плюс undefined даёт NaN. Меняем <= на < — и функция честно возвращает 4.
console.logс метками входов и выходов — самый быстрый способ увидеть, что происходит внутри функции.- Оператор
debuggerставит выполнение на паузу, но только при открытых DevTools; перед публикацией его убирают. - В панели Sources брейкпоинт ставится кликом по номеру строки, а панель Scope показывает все переменные в точке паузы.
- Step over выполняет строку целиком, Step into заходит внутрь вызова, Resume продолжает до следующей остановки.
- Цикл поиска бага: воспроизвести → сузить логами или брейкпоинтами → проверить гипотезу.
1/3Что произойдёт со строкой debugger;, если инструменты разработчика закрыты?
2/3Функция возвращает NaN. Что залогировать в первую очередь?
3/3Пауза стоит на строке с вызовом функции. Какой кнопкой заглянуть внутрь этой функции?
Странный бонус
Функция начисляет игроку бонусные очки, но вместо 150 выводит 10050. Добавьте внутрь функции console.log со значениями и typeof обоих аргументов и найдите причину.
console.log(typeof score, typeof bonus) печатает number string: бонус пришёл строкой, и + склеил «100» и «50» вместо сложения. Лечится преобразованием: score + Number(bonus).
Пройдите по шагам
Запустите этот код на странице с открытым DevTools и дождитесь паузы на debugger. Кнопкой Step into зайдите в каждый вызов double и подсмотрите в панели Scope, чему равен x.
Первый Step into со строки return заводит внутрь double с x = 3, второй — с x = 4: значение видно в Scope в блоке Local. После этого Resume допечатает в консоль 14.