
Многие задают мне один и тот же вопрос:
«Как дебажить этот $%*!%$! JavaScript?».
Так вот, во-первых JavaScript — не $%*!%$! А как я его дебажу — сейчас расскажу.
(Примечание: наверное эта статья больше для новичков. Так что не судите строго)
Казалось бы — да что тут рассказывать? Всё же очевидно. Но вопрос этот мне задают с завидной частотой. Да и мне есть, что рассказать.
Приведу конкретные примеры и расскажу, как я их решаю.
Видим цель, не видим препятствий
JavaScript вывалил ошибку? Замечательно! Нет, это конечно плохо, но гораздо лучше, чем если бы он промолчал (да, бывает и такое!) в случае ошибки.
Наша цель — понять, что же, чёрт побери, произошло? Но сначала —
Debuggers
Вот основные браузеры и их средства отладки:
Firefox:
Всеми нами любимый плагин FirebugSafari,
Chrome:
Встроенный в WebKit Web InspectorOpera:
Чудесный встроенный DragonflyInternet Explorer 8:
Встроенный Developer ToolsInternet Explorer <= 7
Тут есть множество вариантов:
DebugBar, Companion.JS, через MS Visual Studio…
Но меня все эти штуки как-то не зацепили — все они либо громоздкие, либо неудобные, либо платные :)
А зацепил меня только лишь Script Debugger. Он очень спартанский, но в нём есть всё, что мне нужно.
Во всех этих средствах отладки нас будут интересовать breakpoint'ы:

А вот немного «вкусностей» — conditional breakpoints (правый клик по «бряке»):

То есть заводим глобальную переменную (к примеру)
allowBreakpoints
и «бряки» будут срабатывать только тогда, когда мы сами того захотим.К сожалению, работает не везде, поэтому я это обычно не использую.
Как «тормознуть» поток
Ключевое слово
debugger
. Увидав такое в коде, любой уважающий себя JS-debugger остановит поток JavaScript и покажет нам место остановки
Можно смело пользоваться в:
Firefox со включенным Firebug'ом
Safari,
Chrome с открытым/включённым Web Inspector/Script Panel
Internet Explorer 8 с открытым/включённым Developer Tools
Internet Explorer <= 7 с установленным Script Debugger
Opera с открытым/включённым Dragonfly
И не бойтесь писать
debugger
в вашем коде — ошибки это нигде не вызовет.А вот вариант с условной остановкой:
if (allowBreakpoints == true)
debugger;
* This source code was highlighted with Source Code Highlighter.
Мне так нравится гораздо больше, чем ставить «бряку»: так я пишу код и дебажу его по сути в одном месте, а не в двух.
Debug через alert()
Это наименее информативный debug, который к тому же останавливает поток JavaScript. Да к тому же модальный по отношению к браузеру. Забудьте, что он вообще существует.
Особенность breakpoint'ов
Рассмотренные варианты все, как один, тормозят поток JavaScript. Это плохо!
Почему? Если в момент остановки скрипта у вас был запущен AJAX-запрос или Timeout, и ответ не успел вернутся — он может уже не вернутся никогда. Согласитесь, в современных web-проектах этого добра хватает. Поэтому в момент «экстренной остановки» скрипта мы уже не сможем адекватно debug'ать дальше — часть логики будет безвозвратно утеряна.
Поэтому я стараюсь избегать на практике debug с остановкой.
«Debugging JavaScript with breakpoints is bad, mmkay?» © Mr. Mackey, South Park
Однако: breakpoint есть breakpoint, и если вы исследуете ну очень запущенный баг — тут без остановки не обойтись (надо будет сделать watch текущим переменным и т.д.)
«Правильный» debug
Коротко: хороший debug — через logging. Так я в основном и работаю — в нужном месте в нужное время срабатывает
console.log(...)
.Да, насчёт
console.log
— впервые этот метод увидел мир, насколько я помню, вместе с Firebug. Никакой это не стандарт и не факт, что оно заработает в IE6. Однако современные браузеры вводят logging именно как console.log
. Это так, к сведению. И если в продакшн попадёт код с console.log(...)
— будьте на чеку, может поломаться! Так что может быть стоит переопределить у себя в коде объект console
, так, на всякий пожарный.Если же в целевом браузере нет
console.log
, а хочется — попробуйте Firebug Lite или Blackbird, может понравится ;)Пример №1
JavaScript показал ошибку. Надо понять — что к чему.
Включаем в debugger'е режим «Break On Error»:

Воспроизводим ошибку снова. JavaScript останавливается. Видим место ошибки, делаем watch и точно определяем, в чём же дело.
Пример №2
CASE:
JavaScript не показал ошибку. Но вы знаете, что она есть (как суслик). Да, такое иногда бывает.
CASE:
Надо просто продебажить некий код. Скажем, посмотреть, что происходит по нажатию кнопки или после AJAX-загрузки данных.
Тут сложней — надо найти, с чего начать.
Немного искусства
Поиск «точки входа» JavaScript'а — непростая штука. Вот как я это делаю:
- Самое главное — разбираться в средстве разработки. Будь то jQuery, или ExtJS, или Mootools, или вообще свой собственный framework — нужно понимать, как создаётся кнопка, как «навешивается» обработчик события, как данные ходят в AJAX, как попадают в grid, как работает TinyMCE RTE, как, как, как… Если нет понимания задачи — не получится её решить!
- Используем Inspect нашего debugger'а (если нет Inspect'а — используйте всё тот же Firebug Lite):
- Находим нужный элемент HTML (например, кнопку)
- Ищем ближайший от него элемент с осмысленным ID (н-р: id=«my-super-button»; а id=«ext-gen124» уже не подходит) вверх по иерархии (это может быть и сама кнопка, а может быть DIV четырмя уровнями выше)
- Ищём в нашем коде вхождение этого осмысленного ID'шника
- Нашли. Отлично, теперь вдумчиво читаем код и находим нужное место (обработчик нажатия кнопки, AJAX-запрос и т.д.)
- Пишем в нужном месте
debugger
:// условная остановка
if (allowBreakpoints == true)
debugger;
// или просто
debugger;
* This source code was highlighted with Source Code Highlighter.
Так, значит место в коде нашли, бряку поставили. Если не хочется (или просто нельзя) изменять исходный код — можно вместо ключевого слова
debugger
поставить brakepoint в средстве отладки.Пример №3
Тот же случай: надо продебажить некий код. Скажем, посмотреть, что происходит по нажатию кнопки или после AJAX-загрузки данных. Но в этот раз мы не можем тормозить поток JavaScript по описанным мной причинам.
Итак:
- Ищем нужное место тем же способом
- Вместо
debugger
пишемconsole.log(variable_to_watch)
Тут есть интересные модернизации.
CASE UNO
variable_to_watch
— объект, который изменился с момента вывода в консоль. А хочется увидить его состояние именно на момент вызова. Тут надо использовать не
console.log(variable_to_watch)
, а console.dir(variable_to_watch)
CASE DUO
Нужно не просто увидеть текущее значение
variable_to_watch
, но ещё и поэкспериментировать с ним (например, хочется вызвать его метод):// пусть хочется получить доступ к объекту obj
if (debugEnabled)
console.log(window.temp_var = obj);
* This source code was highlighted with Source Code Highlighter.
Таким образом мы не только увидим вывод в консоли, но и получим доступ к объекту через глобальную ссылку на него:
window.temp_var
. Открываем Firebug->Console и вызваем метод:
temp_var.objMethod()
.Нет консоли? Пишем в адресной строке:
javascript:alert(temp_var.objMethod()); void 0;
Пример №4
Ещё один пример. Возможно, немного странный. Хочется продебажить метод 3d-party-framework'а (например, ExtJS), но вот беда — нельзя тормозить JavaScript и нет доступа к исходному коду (правда странный пример? :)
Что же делать? Я делаю так:
Создаём файл с патчем: my-ext-patch.js, и подключаем его после ext-all.js
Внутри пишем что-то вроде:
(function() {
var _backup = Ext.form.Form.render; // Резервируем метод рендера формы. -- Ваш Кэп ;)
Ext.form.Form.render = function(container) { // Wrap'им метод
// А вот и дебаг
console.log(container);
// Возможны варианты:
// console.dir(container);
// console.log(window.t = container);
// debugger;
// Выполняем начальный метод
return _backup.apply(this, arguments);
}
})();
* This source code was highlighted with Source Code Highlighter.
Извращение? Возможно. Но мне нравится >:)
Эпилог
Вот так можно дебажить
Надеюсь, что мой опыт кому-то поможет.