Pull to refresh

Отладка Javascript

JavaScript *
Debug Logo

Многие задают мне один и тот же вопрос:
«Как дебажить этот $%*!%$! JavaScript?».

Так вот, во-первых JavaScript — не $%*!%$! А как я его дебажу — сейчас расскажу.

(Примечание: наверное эта статья больше для новичков. Так что не судите строго)


Казалось бы — да что тут рассказывать? Всё же очевидно. Но вопрос этот мне задают с завидной частотой. Да и мне есть, что рассказать.

Приведу конкретные примеры и расскажу, как я их решаю.

Видим цель, не видим препятствий


JavaScript вывалил ошибку? Замечательно! Нет, это конечно плохо, но гораздо лучше, чем если бы он промолчал (да, бывает и такое!) в случае ошибки.

Наша цель — понять, что же, чёрт побери, произошло? Но сначала — рекламная пауза лирическое отступление: средства JavaScript Debug'а в основных браузерах.

Debuggers


Вот основные браузеры и их средства отладки:
  • Firefox Logo Firefox:
    Всеми нами любимый плагин Firebug
  • Safari Logo Safari, Chrome Logo Chrome:
    Встроенный в WebKit Web Inspector
  • Opera Logo Opera:
    Чудесный встроенный Dragonfly
  • IE Logo Internet Explorer 8:
    Встроенный Developer Tools
  • IE Logo Internet Explorer <= 7
    Тут есть множество вариантов:
    DebugBar, Companion.JS, через MS Visual Studio
    Но меня все эти штуки как-то не зацепили — все они либо громоздкие, либо неудобные, либо платные :)
    А зацепил меня только лишь Script Debugger. Он очень спартанский, но в нём есть всё, что мне нужно.

Во всех этих средствах отладки нас будут интересовать breakpoint'ы:

Debuggers Screenshot 1

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

Debuggers Screenshot 2

То есть заводим глобальную переменную (к примеру) allowBreakpoints и «бряки» будут срабатывать только тогда, когда мы сами того захотим.
К сожалению, работает не везде, поэтому я это обычно не использую.

Как «тормознуть» поток


Ключевое слово debugger. Увидав такое в коде, любой уважающий себя JS-debugger остановит поток JavaScript и покажет нам место остановки

Debugger Keyword Screenshot 1

Можно смело пользоваться в:
  • Firefox Logo Firefox со включенным Firebug'ом
  • Safari Logo Safari, Chrome Logo Chrome с открытым/включённым Web Inspector/Script Panel
  • IE Logo Internet Explorer 8 с открытым/включённым Developer Tools
  • IE Logo Internet Explorer <= 7 с установленным Script Debugger
  • Opera Logo 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'а — непростая штука. Вот как я это делаю:
  1. Самое главное — разбираться в средстве разработки. Будь то jQuery, или ExtJS, или Mootools, или вообще свой собственный framework — нужно понимать, как создаётся кнопка, как «навешивается» обработчик события, как данные ходят в AJAX, как попадают в grid, как работает TinyMCE RTE, как, как, как… Если нет понимания задачи — не получится её решить!
  2. Используем Inspect нашего debugger'а (если нет Inspect'а — используйте всё тот же Firebug Lite):
    1. Находим нужный элемент HTML (например, кнопку)
    2. Ищем ближайший от него элемент с осмысленным ID (н-р: id=«my-super-button»; а id=«ext-gen124» уже не подходит) вверх по иерархии (это может быть и сама кнопка, а может быть DIV четырмя уровнями выше)
  3. Ищём в нашем коде вхождение этого осмысленного ID'шника
  4. Нашли. Отлично, теперь вдумчиво читаем код и находим нужное место (обработчик нажатия кнопки, AJAX-запрос и т.д.)
  5. Пишем в нужном месте debugger:
    // условная остановка
    if (allowBreakpoints == true)
      debugger;

    // или просто
    debugger;

    * This source code was highlighted with Source Code Highlighter.
Конечно данный способ не идеален. Бывает, что даёт промашки. Но это хороший способ, мне он сильно помагает в работе.

Так, значит место в коде нашли, бряку поставили. Если не хочется (или просто нельзя) изменять исходный код — можно вместо ключевого слова debugger поставить brakepoint в средстве отладки.

Пример №3


Тот же случай: надо продебажить некий код. Скажем, посмотреть, что происходит по нажатию кнопки или после AJAX-загрузки данных. Но в этот раз мы не можем тормозить поток JavaScript по описанным мной причинам.

Итак:
  1. Ищем нужное место тем же способом
  2. Вместо 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.

Извращение? Возможно. Но мне нравится >:)

Эпилог


Вот так можно дебажить «этот $%*!%$!» JavaScript. Важно сосредоточиться на первых трёх примерах.

Надеюсь, что мой опыт кому-то поможет.
Tags:
Hubs:
Total votes 192: ↑178 and ↓14 +164
Views 143K
Comments Comments 78