Небольшой отладчик в Javascript

Недавно столкнулся в исследованиях с проектом trimpath. Лично для себя ничего интересного не обнаружил, кроме интересного способа расстановки точек останова (я его немного видоизменил от оригинала — иначе длинные строки некорректно отображались в опере и ИА)

  1. breakpoint = function (_name, _context)
  2. {
  3.     if(!breakpoint.allow)
  4.     {
  5.         return;
  6.     }
  7.     var expr, result, output;
  8.     while(true)
  9.     {
  10.         expr = prompt('Debug into breakpoint "' + _name + '"' , '');
  11.         if(!expr)
  12.         {
  13.             break;
  14.         }
  15.         else
  16.         {
  17.             output = true;
  18.             try
  19.             {
  20.                 result = result = _context(expr);
  21.             }
  22.             catch(e)
  23.             {
  24.                 alert('[!] caught exception with message "' + e.message + '"');
  25.                 output = false;
  26.             }
  27.             if(output)
  28.             {
  29.                 alert(result);
  30.             }
  31.         }
  32.     }
  33. }
  34.  
  35. breakpoint.allow = true;
* This source code was highlighted with Source Code Highlighter.


Теперь чтобы поставить току останова нужно написать что-то вроде этого:

  1. function f(_a, _b, _c)
  2. {
  3.     var a = 10;
  4.     var b = 20;
  5.     var c = 30;
  6.     breakpoint("breakpoint1", function (_$) { return eval(_$); });
  7. }
  8.  
  9. f(40, 50, 60);
* This source code was highlighted with Source Code Highlighter.


Обратите внимание на функцию function (_$) { return eval(_$); } — так точке останова передаётся контекст вызывающей функции. Имя _$ использовано как редко встречаемый идентификатор.

Пригодиться этот механизм может там, где нет фаербага или других нормальных отладчиков, например в ИА. Просто и со вкусом. :^)

Прошу прощения, если написал боян — для меня это было новым подходом.

UPD#1 08.12.09

Если изменить код точек останова вот так:

  1. debug = true;
  2.  
  3. breakpoint = '(new Breakpoint(\'anonymous\')).exec(function (_$) { return eval(_$); });';
  4.  
  5. Breakpoint = function (_name)
  6. {
  7.     this.name = _name ? 'anonymous' : _name;
  8.     this.allow = true;
  9. }
  10.  
  11. Breakpoint.prototype.exec = function (_context)
  12. {
  13.     if(!(debug && this.allow))
  14.     {
  15.         return;
  16.     }
  17.     ...
  18. }
* This source code was highlighted with Source Code Highlighter.


то можно будет писать вот так:

  1. eval(breakpoint);
* This source code was highlighted with Source Code Highlighter.


Спасибо asmolianinov за идею (см. комментарии).
+12
3 декабря 2009, 17:22
22
akaj 9,0

комментарии (23)

0
Mephistophele #
а можно каким-нибудь образом это сократить до
breakpoint function (_$) { return eval(_$); };
?
0
akaj #
можно до breakpoint(function (_$) { return eval(_$); }); (со скобками) — просто уберите параметр name.

Или речь о показе контекста брэйка?
+1
jil #
Страшно стало от мысли — поставить такой брейкпоинт в цикле… Сколько раз надо нажать «нет, пока не останавливаемся».
Я понимаю что можно поставить кучу условий на возникновение брейка, но часто эти условия становятся известны уже после отладки, а до этого: приходится следить за многими параметрами.
+1
akaj #
Насчёт условий входа — спасибо за мысль, я займусь. А насчёт цикла — что поделать, в этом суть точки останова. :^) Или я неверно понял вашу мысль?
0
jil #
Я не понял Вашу мысль, что именно Вы не поняли в моей мысли? :)
В цикле for(var i=0;i<x;i++) нет проблем, а вот в while(true){… (if(somеthing) break)^n ...} очень трудно представить, где бряку ставить + когда ей срабатывать.

Вообще сакральный замысел «изобретения» непонятен.
Особенно учитывая ниже сказанное Во всех браузерах есть отладчики: habrahabr.ru/blogs/javascript/76485/
0
akaj #
> while(true){… (if(somеthing) break)^n ...}

Если честно, то «вечные» циклы лучше не писать совсем — всегда имеет смысл добавлять сто-то типа __continue в качестве условия продолжения в целях удобства отладки: while(__continue)…
А в такой точке останова можно написать __continue = false, и всё такое… :^) Можно сделать из всего этого большой отладчик с условиями, отловом ошибок и т.д. — только зачем, если такие уже есть? Вы же не станете отрицать, что иногда испольуете alert в целях отладки? :^)

> Вообще сакральный замысел «изобретения» непонятен.

Да нет никакого особо сакрального смысла. Просто может, где-то пригодится — иногда очень нужно протестировать состояние скрипта там, где отладчик не установлен (комп Маши, см. ниже). Дебагер не является частью браузера, а доустанавливается отдельно — не будете же вы секретарше дебагер ставить. :^) А рассказывать о «фантомных» багах, я думаю, не нужно — и так известная проблема.
0
bolk #
Во всех браузерах есть отладчики: habrahabr.ru/blogs/javascript/76485/
0
akaj #
Во всех типах браузеров, если точнее. :^) Этот маленький отладчик можно использовать по какой небудь специальной директиве, которая включает отладку (например, двойной клик на копирайт :^), чтобы протестировать код в нужных местах с любого браузера, даже того, где отладчик не установлен (например, комп Маши из бухгалтерии).

Но вообще говоря, способ и не претендует на полноценный отладчик — так, на всякий случай. :^)
0
Aux #
А что такое ИА?

И да, я не знаю ни одного браузера без дебаггера.
0
akaj #
ИА я по привычке называю Internet Explorer (и известный осёл, и созвучно :^).

> И да, я не знаю ни одного браузера без дебаггера.

В ie особо не разгуляешься. Но, может быть, я и не прав. В любом случай, я привёл пример, где брэйк может пригодиться, выше.
0
Ntropy #
Lynx
в мире много чего мы не знаем.
0
david_mz #
А разве задачу передачи контекста нельзя решить при помощи arguments?
https://developer.mozilla.org/En/Core_Javascript_1.5_Reference/Functions_and_function_scope/arguments
0
akaj #
Мне самому не нравится этот аппендикс function ..., но я не смог от него избавиться (хотя очень пытался) — возможно, удастся это сделать в будущем. Проблема в том, что передать контекст функции в eval можно, но не везде. А объект VO (var object) — это не более чем «олицетворение» из стандартов ECMAscript.
+1
asmolianinov #
Не могли бы вы подробнее остановиться на «передать контекст функции в eval можно, но не везде»?
Что имеется ввиду?
+1
akaj #
Вот в этой статье вы можете найти пример, когда eval'у передаётся два аргумента — выражение и контекст. Сейчас механизм работает через with, но применим только к объектам, т.е. к внутренним переменным функции вы так доступ не получите.
+1
asmolianinov #
Спасибо, я не знал про второй аргумент eval!
А как это можно было бы использовать для вашего отладчика?
0
akaj #
Технически, можно было бы передавать ссылку на функцию, чтобы использовать её в eval функции breakpoint. Но это, к сожалению, будет работать только в ранних версиях sidermonkey. Я ищу способ избавиться от аппендикса function ..., но пока безуспешно. Если найдёте способ — поделитесь, если не жалко. :^)
+1
asmolianinov #
Ну что ж, мне кажется способ есть. Что если сделать так:

function breakpoint (name) { 
  return eval('breakpoint.exec("'+ name +'", function (_$) { return eval(_$) })');
}
breakpoint.exec = function (_name, _context)
{
  ... // цикл вашего отладчика
}

После этого в отлаживаемой функции можно просто вызвать:

  breakpoint('name');

или даже просто:

  breakpoint();
0
akaj #
Боюсь, это не совсем то, что нужно. Eval действует в том контексте, в котором его вызвали, так же как любая другая инструкция — т.е. в данном случае return eval('expr') эквивалентно просто return expr, то есть контекст будет задан для функции break, что, в общем-то, ни к чему.

Впрочем, я могу ошибаться, ибо сделать полноценную проверку мне мешает то, что я уже засыпаю. :^) Но я обязательно протестирую пример завтра в начале рабочего дня.

Возможно, вам поможет использование переменной arguments.callee.caller, которая ссылается на функцию, которая вызвала функцию arguments.callee — ведь для функции breakpoint именно к контексту .caller нужно получить доступ.
0
asmolianinov #
Мм… а вот FF вроде согласен с моей идеей )

Мне кажется, return eval('expr') действительно эквивалентно просто return expr, НО вызванному в контексте отлаживаемой функции. Нет?
+1
akaj #
FF, как и любой другой браузер, запустит такую точку останова. Но контекст вызвавшей функции будет, увы, недоступен.

Eval выполняется точно так же, как и любой другой оператор — в том контексте, в котором он вызван. Насколько мне известно, никаких исключений на этот счёт не предусмотрено.

Но ваш пример с eval('breakpoint.exec...') сработает, если вызвать его не внутри функции breakpoint, а непосредственно в отлаживаемой функции. Это даёт нам возможность просто определить константу и писать eval(breakpoint) — не так красиво, как просто breakpoint, но тоже неплохо. :^) Смотрите последний UPD в топике. Благодарю вас за отличную идею!
0
asmolianinov #
Действительно, вы правы. А мне надо раньше ложиться :)

Самое лаконичное что можно придумать — eval(breakpoint). Вот если бы можно было передать второй аргумент eval…
+1
akaj #
Попробую придумать как добиться просто breakpoint(); :^). Если придумаю — обязательно напишу UPD к этому топику.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.