Advanced Debugging with JavaScript
Tags: javascript, debug
Продвинутая отладка в JavaScript
Поглиблений дебаг в JavaScript
| ← A caching pattern for models | Zend Framework and Translation → |
When used effectively, JavaScript debuggers help find and squash errors in your JavaScript code. To become an advanced JavaScript debugger, you’ll need to know about the debuggers available to you, the typical JavaScript debugging workflow, and code requirements for effective debugging. In this article, we’ll discuss advanced debugging techniques for diagnosing and treating bugs using a sample web application.
On accessibility
This article highlights strengths and differences between debugging tools, and shows how we perform advanced JavaScript debugging tasks. Our methods often rely on the mouse; if you prefer to use keyboard shortcuts, or if you rely on assistive technologies such as screen readers to interact with these tools, you should consult the relevant documentation to determine how (or if) these tools will work for you.
The debuggers
With an increasing selection of good debuggers available, JavaScript programmers can gain a lot from learning how to use them. JavaScript debugger user interfaces (UIs) are becoming more polished, more standardized across products, and easier to use, thus making it easier for both experts and novices to learn JavaScript debugging.
Currently, there are debugging tools available for all major web browsers.
Firefox has the well-known Firebug extension.
IE8 (in beta at the time of this writing) comes with built-in Developer Tools.
Opera (versions 9.5 and greater) supports the Opera Dragonfly debugger.
Safari has both the Drosera JS debugger and a DOM viewer called WebInspector. In more recent versions of Safari, the debugger has been integrated into WebInspector.
Currently, Firebug and Dragonfly are the most stable options. IE8’s beta tools sometimes ignore breakpoints, and at the time this article was written, WebInspector seemed to have compatibility issues with the nightly builds of Webkit.
Familiarize yourself with multiple debugging tools—you never know in which browser the next bug will arise. Since the debuggers are roughly comparable in functionality, it’s easy to switch between them once you know how to use one.
Debugging workflow
When investigating a specific problem, you will usually follow this process:
1. Find the relevant code in the debugger’s code view pane.
2. Set breakpoint(s) where you think interesting things may occur.
3. Run the script again by reloading the page in the browser if it’s an inline script, or by clicking a button if it’s an event handler.
4. Wait until the debugger pauses execution and makes it possible to step through the code.
5. Investigate the values of variables. For example, look for variables that are undefined when they should contain a value, or return "false" when you expect them to return "true".
6. If necessary, use the command line to evaluate code or change variables for testing.
7. Find the problem by learning which piece of code or input caused the error conditions.
To create a breakpoint, you can also add the statement debugger to your code:
Hide code highlighting
Debugger requirements
Most debuggers require well-formatted code. Scripts written on one line make it difficult to spot errors in line-by-line debuggers. Obfuscated code can be hard to debug, especially code that has been "packed" and needs to be unpacked using eval(). Many JavaScript libraries allow you to choose between packed/obfuscated and well-formatted versions, making it easy to use formatted code for debugging.
Debugging demo
Let’s start with a small, bug-ridden example, to learn how to diagnose and treat each ailment in turn. Our example is a web application login screen.
Imagine you’re working on this fabulous new web application, and your testers ask you to investigate this list of bugs:
1. The "Loading…" status bar message does not disappear when the application finishes loading.
2. The language defaults to Norwegian even in English versions of Firefox and IE.
3. A global variable named prop is created somewhere.
Launching debuggers
In Firefox you need to make sure you have the Firebug extension installed. Select "Tools > Firebug > Open Firebug" to get started.
In Opera 9.5 beta 2 or later, choose "Tools > Advanced > Developer Tools."
In IE8 beta, choose "Tools > Developer Tools."
In Safari or WebKit, first enable the debug menu, then choose "Debug > Show Web Inspector."
It’s time to fire up the debuggers. To follow the demo, pay close attention to the step-by-step instructions throughout the remainder of the article. Since some instructions involve modifying the code, you may want to save the page locally and load it from your file system before starting.
Bug one: the "Loading…" message in the status bar
If you look at the debugging applications in Dragonfly and Firebug, you’ll get an initial view as seen in figure one.


fig. 1: the initial view of our application’s JavaScript in Dragonfly and Firebug, respectively.
When you look at the source code in the debugger, note that there is a function clearLoadingMessage() defined at the top of the code. This seems like a good place to set a breakpoint.
Here’s how to do it:
1.Click the line number in the left margin to set a breakpoint on the first line inside the function clearLoadingMessage().
2.Reload the page.
Note: the breakpoint must be set on a line with code that will execute when the function runs. The line that contains function clearLoadingMessage(){ is just the function signature. Setting a breakpoint there will never actually cause the debugger to stop. Set your breakpoint on the first line inside the function instead.
When the page is reloaded, script execution stops at the breakpoint and you’ll see an output like the one shown in figure two. (Dragonfly is shown at the top, Firebug at the bottom.)


fig. 2: the debuggers stopped at a breakpoint inside clearLoadingMessage.
Let’s step through the function. You’ll see that it updates two DOM elements, and on line 28 it mentions the word statusbar. That looks significant. It’s likely that getElements( 'p', {'class':'statusbar'} )[0] will find the statusbar element in the DOM. Is there a way you can test that theory quickly?
Paste the relevant snippet into the command line to check your theory. Figure three shows three screenshots (Dragonfly, Firebug, and IE8) after reading the innerHTML or outerHTML of the element returned by the command you’re investigating.
To test this assumption:
1. Find the command line:
In Firebug, switch to the "Console" tab.
In Dragonfly, look below the JavaScript source code pane.
In IE8 Developer Tools, find the tab on the right labeled "Console."
2. Paste getElements( 'p', {'class':'statusbar'} )[0].innerHTML into the command line.
3. Press enter.



fig. 3: output shown in Dragonfly, Firebug, and IE8, respectively.
The command line is a very useful tool that allows you to test small script snippets quickly. Firebug’s console integration is very useful—if your command outputs an object, you get an intelligent view. For example, you get a markup-like representation if it is a DOM object.
You can use the command line to explore the problem in more depth. The line of JavaScript we’re looking at does the following three things:
1.It gets a reference to the status bar element. In the DOM inspector view, you will see that the corresponding markup is <p class="statusbar">
2.It looks up its firstChild, in other words, the first node inside this paragraph element.
3.It sets the innerText property.
Let’s try to run a bit more of that command from the command line. (Tip: use the up arrow key to navigate back to previous lines typed into the command line field.) For example, you might wonder what the current value of this element’s innerText property is before it is set. To check this, you can type the entire command up to the equals sign into the command line:
Hide code highlighting
Surprisingly, the output is…nothing. So the expression getElements( 'p', {'class':'statusbar'} )[0].firstChild points to something in the DOM that does not contain any text, or does not have an innerText property.
So, the next question is: what exactly is the first child node of the paragraph element? Let’s ask the command line that question. (See figure four for the result.)

fig. 4: the Dragonfly debugger command line, outputting [object Text].
Dragonfly’s [object Text] output shows that this is a DOM text node. Firebug shows us the content of the text node as a link to the DOM explorer. You have now found the problem that causes bug number one: a text node does not have an innerText property—only element nodes do. Hence, setting p.firstChild.innerText does nothing. This bug can be fixed by replacing innerText with nodeValue, which is a property the W3C DOM standard defines on text nodes.
After you’ve had a chance to review this example:
1. Press [F5] or the run button to finish the script, now that you’ve found the problem.
2. Remember to clear the old breakpoint by clicking the line number again.
Bug two: problematic language detection
You may have noticed the var lang; /*language*/ line near the top of the JavaScript—the code that sets this variable is probably involved when things go wrong. You can find things in the code quickly using the handy search functions provided by both debuggers. In Dragonfly it’s just above the code viewer; in Firebug it’s on the top right side of the Firebug user interface. (See figure five.)
To find the place that deals with the application’s localization:
1. Type lang = into the search field.
2. Set a breakpoint on the line where the lang variable is assigned a value.
3. Reload the page.
Safari’s WebInspector also has a very powerful search feature. WebInspector allows you to search all the code at the same time, including markup, CSS, and JavaScript. The results are shown in a dedicated pane where you can doubleclick them to jump to the source line, as shown in the screenshot.


fig. 5: searching with the Dragonfly and WebInspector debuggers.
To inspect what this function does:
1. Use the "step into" button to enter the getLanguage function.
2. Click the "step into" button repeatedly to go through the code one line at a time.
3. Keep an eye on the overview of local variables to see how they change while you step through the function.
Stepping into the getLanguage function, you will see that it tries reading the language from the user-agent string. The author of this code noticed that some language information is included in the user-agent string on some browsers, and tries to parse navigator.userAgent to extract this information:
Hide code highlighting
While stepping through this code with the debuggers, you can use the local variables overview. Figure six shows Firebug and IE8 Developer Tools with the ar1 array expanded to show the elements in it:

fig. 6: Firebug and IE8’s local variable panes, while running the getLanguage function.
The ar1[i].match(/^(.{2})$/) statements simply look for a string that is exactly two characters long, expecting a two-character language code such as en or no. However, as you can see from the screenshots, Firefox’s language information in the user-agent string is actually nn-NO. IE has no language-related information in this part of the userAgent string.
The second bug has been found: the language detection looks for a two-letter language code, but Firefox has a five letter locale string and IE has nothing. This "language detection" code should probably be scrapped altogether, and replaced with something server-side that uses the HTTP Accept-Language header or possibly falls back to reading navigator.language. Or navigator.userLanguage in IE. One example of what that function might look like is shown below:
Hide code highlighting
Bug three: the mystery prop variable


fig. 7: Firebug and Dragonfly’s local variable panes showing the global prop variable.
In figure seven, you can clearly see the mystery prop variable. Well-written applications keep the number of global variables to a minimum, because they can cause confusion when different sections of the application try to use the same variable name. Imagine that tomorrow, another team of developers added a new feature to our application and also named a variable prop. We’d have two different parts of our application trying to use the same variable name for different things. This practice is a recipe for conflicts and bugs down the road. Hence, you have to track down where this variable is set and see if there is a way to make it a local variable. You could start searching like we did to find bug two above, but perhaps there is a smarter way…
Debuggers for many other programming languages have a "watch" concept that can break into the debugger when a variable changes. Neither Dragonfly nor Firebug currently support "watch" but it’s easy to get the same effect by adding the following line of debugging code at the top of the source of the script you’re troubleshooting:
Hide code highlighting
To add this "watch"-like functionality to the debugged script:
1. Add the debugging code to the top of the first script.
2. Reload the page.
3. Note how it breaks when the problem occurs.
Using getters and setters can emulate "watch" functionality and help you set "smart" breakpoints.
The IE8 Developer Tools have a "watch" pane, but it doesn’t break when a watched variable is modified. Given IE8’s incomplete support for getters and setters, you can’t emulate this functionality the way you can in Firefox, Opera, and Safari.
When you reload the application, it will immediately break where the global variable prop is being defined. It actually stops at the line of debugging code you added because this is where the debugger statement is. One click on the "step out" button will take you from the setter function to the place where the variable is set. This code is found inside the getElements function:
Hide code highlighting
It now stops just below the line that starts with for (prop. Here, you can see that the prop variable is used without being defined as a local variable, with a var keyword inside the function. Simply changing it to for (var prop will fix our third bug.
Summary
This article demonstrates the basics of using debuggers and some advanced JavaScript debugging techniques. You’ve learned how to set break points both from the debugger and from scripts, how to step through code, how to use the debugger user interface, how to set advanced break points, and how to integrate bookmarklets in your debugging techniques.
If you had problems following the more advanced parts of this article, don’t worry! Mastering the basics first will make you a much better developer and eventually, you will develop your own set of interesting techniques to share.
Original: Advanced Debugging with JavaScript
On accessibility
This article highlights strengths and differences between debugging tools, and shows how we perform advanced JavaScript debugging tasks. Our methods often rely on the mouse; if you prefer to use keyboard shortcuts, or if you rely on assistive technologies such as screen readers to interact with these tools, you should consult the relevant documentation to determine how (or if) these tools will work for you.
The debuggers
With an increasing selection of good debuggers available, JavaScript programmers can gain a lot from learning how to use them. JavaScript debugger user interfaces (UIs) are becoming more polished, more standardized across products, and easier to use, thus making it easier for both experts and novices to learn JavaScript debugging.
Currently, there are debugging tools available for all major web browsers.
Firefox has the well-known Firebug extension.
IE8 (in beta at the time of this writing) comes with built-in Developer Tools.
Opera (versions 9.5 and greater) supports the Opera Dragonfly debugger.
Safari has both the Drosera JS debugger and a DOM viewer called WebInspector. In more recent versions of Safari, the debugger has been integrated into WebInspector.
Currently, Firebug and Dragonfly are the most stable options. IE8’s beta tools sometimes ignore breakpoints, and at the time this article was written, WebInspector seemed to have compatibility issues with the nightly builds of Webkit.
Familiarize yourself with multiple debugging tools—you never know in which browser the next bug will arise. Since the debuggers are roughly comparable in functionality, it’s easy to switch between them once you know how to use one.
Debugging workflow
When investigating a specific problem, you will usually follow this process:
1. Find the relevant code in the debugger’s code view pane.
2. Set breakpoint(s) where you think interesting things may occur.
3. Run the script again by reloading the page in the browser if it’s an inline script, or by clicking a button if it’s an event handler.
4. Wait until the debugger pauses execution and makes it possible to step through the code.
5. Investigate the values of variables. For example, look for variables that are undefined when they should contain a value, or return "false" when you expect them to return "true".
6. If necessary, use the command line to evaluate code or change variables for testing.
7. Find the problem by learning which piece of code or input caused the error conditions.
To create a breakpoint, you can also add the statement debugger to your code:
Hide code highlighting
1 2 3 4 5 | function frmSubmit(event){ event = event || window.event; debugger; var form = this; } |
Debugger requirements
Most debuggers require well-formatted code. Scripts written on one line make it difficult to spot errors in line-by-line debuggers. Obfuscated code can be hard to debug, especially code that has been "packed" and needs to be unpacked using eval(). Many JavaScript libraries allow you to choose between packed/obfuscated and well-formatted versions, making it easy to use formatted code for debugging.
Debugging demo
Let’s start with a small, bug-ridden example, to learn how to diagnose and treat each ailment in turn. Our example is a web application login screen.
Imagine you’re working on this fabulous new web application, and your testers ask you to investigate this list of bugs:
1. The "Loading…" status bar message does not disappear when the application finishes loading.
2. The language defaults to Norwegian even in English versions of Firefox and IE.
3. A global variable named prop is created somewhere.
Launching debuggers
In Firefox you need to make sure you have the Firebug extension installed. Select "Tools > Firebug > Open Firebug" to get started.
In Opera 9.5 beta 2 or later, choose "Tools > Advanced > Developer Tools."
In IE8 beta, choose "Tools > Developer Tools."
In Safari or WebKit, first enable the debug menu, then choose "Debug > Show Web Inspector."
It’s time to fire up the debuggers. To follow the demo, pay close attention to the step-by-step instructions throughout the remainder of the article. Since some instructions involve modifying the code, you may want to save the page locally and load it from your file system before starting.
Bug one: the "Loading…" message in the status bar
If you look at the debugging applications in Dragonfly and Firebug, you’ll get an initial view as seen in figure one.


fig. 1: the initial view of our application’s JavaScript in Dragonfly and Firebug, respectively.
When you look at the source code in the debugger, note that there is a function clearLoadingMessage() defined at the top of the code. This seems like a good place to set a breakpoint.
Here’s how to do it:
1.Click the line number in the left margin to set a breakpoint on the first line inside the function clearLoadingMessage().
2.Reload the page.
Note: the breakpoint must be set on a line with code that will execute when the function runs. The line that contains function clearLoadingMessage(){ is just the function signature. Setting a breakpoint there will never actually cause the debugger to stop. Set your breakpoint on the first line inside the function instead.
When the page is reloaded, script execution stops at the breakpoint and you’ll see an output like the one shown in figure two. (Dragonfly is shown at the top, Firebug at the bottom.)


fig. 2: the debuggers stopped at a breakpoint inside clearLoadingMessage.
Let’s step through the function. You’ll see that it updates two DOM elements, and on line 28 it mentions the word statusbar. That looks significant. It’s likely that getElements( 'p', {'class':'statusbar'} )[0] will find the statusbar element in the DOM. Is there a way you can test that theory quickly?
Paste the relevant snippet into the command line to check your theory. Figure three shows three screenshots (Dragonfly, Firebug, and IE8) after reading the innerHTML or outerHTML of the element returned by the command you’re investigating.
To test this assumption:
1. Find the command line:
In Firebug, switch to the "Console" tab.
In Dragonfly, look below the JavaScript source code pane.
In IE8 Developer Tools, find the tab on the right labeled "Console."
2. Paste getElements( 'p', {'class':'statusbar'} )[0].innerHTML into the command line.
3. Press enter.



fig. 3: output shown in Dragonfly, Firebug, and IE8, respectively.
The command line is a very useful tool that allows you to test small script snippets quickly. Firebug’s console integration is very useful—if your command outputs an object, you get an intelligent view. For example, you get a markup-like representation if it is a DOM object.
You can use the command line to explore the problem in more depth. The line of JavaScript we’re looking at does the following three things:
1.It gets a reference to the status bar element. In the DOM inspector view, you will see that the corresponding markup is <p class="statusbar">
2.It looks up its firstChild, in other words, the first node inside this paragraph element.
3.It sets the innerText property.
Let’s try to run a bit more of that command from the command line. (Tip: use the up arrow key to navigate back to previous lines typed into the command line field.) For example, you might wonder what the current value of this element’s innerText property is before it is set. To check this, you can type the entire command up to the equals sign into the command line:
Hide code highlighting
1 | getElements( 'p', {'class':'statusbar'} )[0].firstChild.innerText |
Surprisingly, the output is…nothing. So the expression getElements( 'p', {'class':'statusbar'} )[0].firstChild points to something in the DOM that does not contain any text, or does not have an innerText property.
So, the next question is: what exactly is the first child node of the paragraph element? Let’s ask the command line that question. (See figure four for the result.)

fig. 4: the Dragonfly debugger command line, outputting [object Text].
Dragonfly’s [object Text] output shows that this is a DOM text node. Firebug shows us the content of the text node as a link to the DOM explorer. You have now found the problem that causes bug number one: a text node does not have an innerText property—only element nodes do. Hence, setting p.firstChild.innerText does nothing. This bug can be fixed by replacing innerText with nodeValue, which is a property the W3C DOM standard defines on text nodes.
After you’ve had a chance to review this example:
1. Press [F5] or the run button to finish the script, now that you’ve found the problem.
2. Remember to clear the old breakpoint by clicking the line number again.
Bug two: problematic language detection
You may have noticed the var lang; /*language*/ line near the top of the JavaScript—the code that sets this variable is probably involved when things go wrong. You can find things in the code quickly using the handy search functions provided by both debuggers. In Dragonfly it’s just above the code viewer; in Firebug it’s on the top right side of the Firebug user interface. (See figure five.)
To find the place that deals with the application’s localization:
1. Type lang = into the search field.
2. Set a breakpoint on the line where the lang variable is assigned a value.
3. Reload the page.
Safari’s WebInspector also has a very powerful search feature. WebInspector allows you to search all the code at the same time, including markup, CSS, and JavaScript. The results are shown in a dedicated pane where you can doubleclick them to jump to the source line, as shown in the screenshot.


fig. 5: searching with the Dragonfly and WebInspector debuggers.
To inspect what this function does:
1. Use the "step into" button to enter the getLanguage function.
2. Click the "step into" button repeatedly to go through the code one line at a time.
3. Keep an eye on the overview of local variables to see how they change while you step through the function.
Stepping into the getLanguage function, you will see that it tries reading the language from the user-agent string. The author of this code noticed that some language information is included in the user-agent string on some browsers, and tries to parse navigator.userAgent to extract this information:
Hide code highlighting
1 2 3 4 5 6 7 | var str1 = navigator.userAgent.match( /\((.*)\)/ )[1]; var ar1 = str1.split(/\s*;\s*/), lang; for (var i = 0; i < ar1.length; i++){ if (ar1[i].match(/^(.{2})$/)){ lang = ar1[i]; } } |
While stepping through this code with the debuggers, you can use the local variables overview. Figure six shows Firebug and IE8 Developer Tools with the ar1 array expanded to show the elements in it:

fig. 6: Firebug and IE8’s local variable panes, while running the getLanguage function.
The ar1[i].match(/^(.{2})$/) statements simply look for a string that is exactly two characters long, expecting a two-character language code such as en or no. However, as you can see from the screenshots, Firefox’s language information in the user-agent string is actually nn-NO. IE has no language-related information in this part of the userAgent string.
The second bug has been found: the language detection looks for a two-letter language code, but Firefox has a five letter locale string and IE has nothing. This "language detection" code should probably be scrapped altogether, and replaced with something server-side that uses the HTTP Accept-Language header or possibly falls back to reading navigator.language. Or navigator.userLanguage in IE. One example of what that function might look like is shown below:
Hide code highlighting
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function getLanguage() { var lang; if (navigator.language) { lang = navigator.language; } else if (navigator.userLanguage) { lang = navigator.userLanguage; } if (lang && lang.length > 2) { lang = lang.substring(0, 2); } return lang; } |
Bug three: the mystery prop variable


fig. 7: Firebug and Dragonfly’s local variable panes showing the global prop variable.
In figure seven, you can clearly see the mystery prop variable. Well-written applications keep the number of global variables to a minimum, because they can cause confusion when different sections of the application try to use the same variable name. Imagine that tomorrow, another team of developers added a new feature to our application and also named a variable prop. We’d have two different parts of our application trying to use the same variable name for different things. This practice is a recipe for conflicts and bugs down the road. Hence, you have to track down where this variable is set and see if there is a way to make it a local variable. You could start searching like we did to find bug two above, but perhaps there is a smarter way…
Debuggers for many other programming languages have a "watch" concept that can break into the debugger when a variable changes. Neither Dragonfly nor Firebug currently support "watch" but it’s easy to get the same effect by adding the following line of debugging code at the top of the source of the script you’re troubleshooting:
Hide code highlighting
1 | __defineSetter__('prop', function() { debugger; }); |
To add this "watch"-like functionality to the debugged script:
1. Add the debugging code to the top of the first script.
2. Reload the page.
3. Note how it breaks when the problem occurs.
Using getters and setters can emulate "watch" functionality and help you set "smart" breakpoints.
The IE8 Developer Tools have a "watch" pane, but it doesn’t break when a watched variable is modified. Given IE8’s incomplete support for getters and setters, you can’t emulate this functionality the way you can in Firefox, Opera, and Safari.
When you reload the application, it will immediately break where the global variable prop is being defined. It actually stops at the line of debugging code you added because this is where the debugger statement is. One click on the "step out" button will take you from the setter function to the place where the variable is set. This code is found inside the getElements function:
Hide code highlighting
1 2 | for (prop in attributes) { if (el.getAttribute(prop) != attributes[prop]) includeThisElement = false; |
It now stops just below the line that starts with for (prop. Here, you can see that the prop variable is used without being defined as a local variable, with a var keyword inside the function. Simply changing it to for (var prop will fix our third bug.
Summary
This article demonstrates the basics of using debuggers and some advanced JavaScript debugging techniques. You’ve learned how to set break points both from the debugger and from scripts, how to step through code, how to use the debugger user interface, how to set advanced break points, and how to integrate bookmarklets in your debugging techniques.
If you had problems following the more advanced parts of this article, don’t worry! Mastering the basics first will make you a much better developer and eventually, you will develop your own set of interesting techniques to share.
Original: Advanced Debugging with JavaScript
Rating:




<< Please, rate this articleRelated articles:
Understanding scope in object oriented JavaScript
JavaScript defining and using custom events
Using jQuery for Background Image Animations