Debugging JavaScript Code - Pro JavaScript Techniques, Second Edition (2015)

Pro JavaScript Techniques, Second Edition (2015)

4. Debugging JavaScript Code

John Resig1, Russ Ferguson1 and John Paxton1

(1)

NJ, United States

Sometimes it’s not the writing of code, but the management of it that gets to us, that drives us up a wall and back to our favorite video game. Why does it work on this machine, not that one? What do you mean, double-equals (==) is bad and triple-equals (===) is good? Why is running tests such a hassle? How should I package this code for distribution? We are plagued by questions, distracted by questions that do not directly bear on the code we are writing.

Of course, we should not ignore these issues. We want to write code of the highest quality, and when we fall short, we want access to easy-to-use debugging tools. We want good test coverage, both for now and for future refactorings. And we should think about how our code will be distributed down the line. That is what this chapter is all about.

We will start by looking at how to solve problems with our code. We would love to be perfect programmers, writing everything correctly the first time. But we all know that does not happen in the real world. So let’s start with debugging tools.

Debugging Tools

All of the modern browsers have some form of developer’s toolkit. Even benighted Internet Explorer 8 had a rudimentary debugger, although you needed administrator access to install it. What we have now is a far cry from the days of development with various alert() statements or the occasional logging to a DOM element as our only recourse.

In general, a developer’s toolkit will have the following utilities:

· The console: A combination JavaScript scratch pad and logging location for our applications.

· A debugger: The tool that eluded JavaScript developers for so long.

· A DOM inspector: Much of our work concentrates on manipulating the DOM, and right-clicking to choose View Source won’t cut it. The inspector should reflect the current state of the DOM (not the original source). Most DOM inspectors go with a tree-based view, with an option to select a DOM element by clicking it in either the inspector or the page itself.

· A network analyzer: Show me what files were requested, which files were actually found, and how long it took to download them.

· A profiler: These are often somewhat crude, but they’re better than wrapping a call in a pair of calls to new Date().getTime().

There are also extensions that can be added to browsers to give you extra debugging capability that goes beyond what is built into the browser. For example, Postman ( http://getpostman.com ) is an extension for Chrome that will let you create any HTTP request and see what the response is. Another popular extension is Firebug ( http://getfirebug.com ), an open source project that adds all the developer tools to Firefox and can also have its own set of extensions.

In this chapter we will refer to the common set of tools as the developer’s tools or the developer’s toolkit, unless discussing a specific browser’s toolset.

The Console

The console is where we spend a lot of our time as developers. The console interface was modeled after the familiar logging levels on most applications: debug, info, warn, error, and log. Often, we first encounter it as a replacement for alert() statements in our code, especially when debugging. On some older versions of IE, only log is supported, but as of IE 11, all five functions are supported. Additionally, the console has a dir() function, which will give you a recursive, tree-based interface to an object. On the off-chance that the console is not present on your platform of choice, try Listing 4-1 as a polyfill.

Listing 4-1. A Console Polyfill

if (!window.console) {

window.console = {

log : alert

}

}

(Obviously, this is only a polyfill for the log function. Were you to use others, you would have to add them individually.)

The output of the various levels varies little. On Chrome or Firefox, console.error includes an automatic stack trace. The other browsers (and native Firefox) simply add an icon and change the text color to differentiate the various levels. Perhaps the main reason to use the various function levels is that they can be filtered out on all three major browsers. Listing 4-2 provides some test code, followed by screen shots from each of the major browsers: Chrome, Firefox, and Internet Explorer (Figures 4-1 through 4-3).

A978-1-4302-6392-0_4_Fig1_HTML.jpg

Figure 4-1.

Test code viewed in Chrome 40.0

A978-1-4302-6392-0_4_Fig2_HTML.jpg

Figure 4-2.

Test code viewed in Firefox 35.0.1

A978-1-4302-6392-0_4_Fig3_HTML.jpg

Figure 4-3.

Test code viewed in Internet Explorer 11.0

Listing 4-2. Console Levels

console.log( 'A basic log message.' );

console.debug( 'Debug level.' );

console.info( 'Info level.' );

console.warn( 'Warn level.' );

console.error( 'Error level (possibly with a stacktrace).' );

var person = {

name : 'John Connelly',

age : 56,

title : 'Teacher',

toString: function() {

return this.name + ' is a ' + this.age + '-year-old ' + this.title + '.';

}

};

console.log( 'A person: ' );

console.dir( person );

console.log( 'Person object (implicit call to toString()): ' + person );

console.log( 'Person object as argument, similar to console.dir: ', person );

Leveraging the Console Features

So what’s the best way to use these console functions? As with many features of JavaScript, the key is consistency. You and your team should agree on usage patterns, keeping a few things in mind: First and most important is that all of your console statements should be removed by the time you deploy your code for the world to see. There is no need for production code to include console statements, and it is trivially easy to remove invocations of console functions (as you will see later in this chapter). Also remember that debugging, which we will look at soon, can replace logging for one-off needs. In general, use console logging for information about the state of an application: Has it started? Could it find data? What do various complicated objects look like? And so on. Your logging will give you a chronicle of the life of the application, a view into the application’s changing state. If your application is a highway, good logging acts as a sort of mile marker—an indication of progress and a general indicator of where to start searching when problems inevitably arise.

The console is also much more than a logging utility. It is a JavaScript scratch pad. Consoles start in single-line mode, where you can enter JavaScript line-by line. Should you want to enter multiple lines of code, you can switch to multiline mode (enabled via icons in Firefox and IE; in Chrome, simply terminate your lines with Shift+Enter). In single-line mode, you can enter various JavaScript statements, enjoying auto-complete, by hitting either Tab or the right-arrow key. The console also includes a simple history, through which you can move backward and forward with the up- and down-arrow keys. The console maintains state, so variables defined on a previous line (or run of the multiline mode) hang around until you reload the page.

This last feature bears further examination. The console has the entire current state of the JavaScript interpreter available to it. This is incredibly powerful. Have you loaded up jQuery? Then you can enter commands according to its API. Want to check the state of a variable at the end of the page? Or maybe you need to look at what’s going on with a particular animation? The console is your friend here. You can call functions, examine variables, manipulate the DOM, and so on. Think of any commands you enter as being added to the just-completed script and having access to all of its state.

The console also has an extended command-line API. Originally created by the fine folks at Firebug, elements of it have been ported to other browsers as well. It is now supported by Chrome and native Firefox, but not by Internet Explorer. There are numerous useful applications of this API, and we wholeheartedly recommend checking out the details at https://getfirebug.com/wiki/index.php/Command_Line_API . Here are a few of the highlights:

· debug(functionName): When functionName is invoked, the debugger will automatically start before the first line of code in the function.

· undebug(functionName): Stops debugging the named function.

· include(url): Pulls a remote script into the page. Very handy if you want to pull in another debugging library, or something that manipulates the DOM differently, or what-have-you.

· monitor(functionName): Turns on logging for all calls to the named function; does not affect console.* calls, but rather inserts a custom call to console.log for each invocation of the function. This logs the function name, its arguments and their values.

· unmonitor(functionName): Turns off logging enabled via monitor() for all calls to the function.

· profile([title]): Turns on the JavaScript profiler; you can pass in an optional title for this profile.

· profileEnd(): Ends the currently running profile and prints a report, possibly with the title specified in the call to profile.

· getEventListeners(element): Gets the event listeners for the provided element.

Thanks to the console, we developers have a full-featured tool for interacting with our code. We can record snapshots of the state of an application, and we can interact with it once it has completed loading. The console will also figure prominently in our next tool, the debugger.

The Debugger

For years, one of the knocks against JavaScript was that it couldn’t be a "real" language because it lacked tools like a debugger. Fast-forward to now, and a debugger is standard equipment with all of the developer toolkits. All current browsers have a developer tools that lets you inspect your application and debug your work. Let’s look at how these tools work, starting with the debugger.

The idea behind the debugger is simple: as a developer, you need to pause the execution of your application and examine its current state. Although we could accomplish the latter part with judiciously applied console.log statements, we cannot take care of the former without a debugger. Once we have paused our application, there are a few tools we need access to. We need a way to tell the debugger to activate. Within the code itself, we can add the simple statement debugger; to activate the debugger at that line. As mentioned earlier, we could also invoke the debug command from the console, passing it the name of a function that, when invoked, will start up the debugger. But the easiest way to pick when the debugger starts is to set a breakpoint.

Breakpoints allow us to run the JavaScript code up to a certain point, and then freeze the application there. When we hit the breakpoint we can then start to understand the current state of the application. From here we can see the content of variables, the scope of these variables, and so on. Also, we have a navigation menu, which includes at least four options: step into the current function (going a layer deeper into the stack), step out of the current function (running the current stack frame to completion and resuming debugging at the point the frame returns to), step over the current function (no need to dive into the function in the first place) and resume execution (run until completion or the next breakpoint).

DOM Inspector

Many JavaScript applications make extensive changes to the state of the DOM—changes so extensive, in fact, that it is often useless to refer to the actual HTML source code mere moments after loading a page. The DOM inspector reflects the current state of the DOM (instead of the state of the DOM when the page was loaded). It should dynamically and instantly update whenever there are changes made to the DOM. Developer tools have included a DOM inspector as a standard feature.

Network Analyzer

Since the previous edition of this book, Ajax has moved from an exotic feature of JavaScript to a standard-issue tool in the professional JavaScript programmer’s bag of tricks. It took a while for debugging tools to catch up. Now, developer tools provide several ways to track Ajax requests. Generally, you should be able to get information on Ajax requests at either the console or the network analyzer. The latter has the more detailed interface. You should be able to sort on specific types of requests (XHR/Ajax, scripts, images, HTML, and so on). Each request should get its own entry, which will usually give you information about the state of the request (both the response code and the response message), where it went to (full URL), how much data was exchanged, and how long the request took. Diving into an individual request, you can see the request and response headers, a preview of processed data and, depending on the data type, a raw view of the data. For example, if your application makes a request for JSON-formatted data, the network analyzer will both tell you about the raw data (a plain string) and, potentially, pass that string through a JSON formatter, so it can show you the end result of the request. Figure 4-4 shows the Network Analyzer in Chrome 40.0, and Figure 4-5 shows it in Firefox 35.0.1.

A978-1-4302-6392-0_4_Fig4_HTML.jpg

Figure 4-4.

Network Analyzerin Chrome 40.0

A978-1-4302-6392-0_4_Fig5_HTML.jpg

Figure 4-5.

Network Analyzer in Firefox 35.0.1

Using both the heap profiler and the timeline, you can detect memory leaks on both the desktop and mobile devices. First let’s look at the timeline.

Timeline

When you first notice your page getting slow, the timeline can quickly help you see how much memory you are using over time. The features in the timeline are very similar in all modern browsers, so to keep this short we are going to focus on Chrome.

Go to the Timeline panel and check off the Memory checkbox. Once there you can click the Record button on the left side. This will start to record the memory consumption of your application. While recording, use your application in a way to expose the memory leak. Stop recording, and the graph will show you how much memory you have been using over time.

If you find that over time your application is using memory and the level is never dropping with garbage collection, then you have a memory leak.

Profiler

If you find that you do have a memory leak, the next step is to look at the profiler and try to understand what is going on.

It’s helpful to understand how memory works in the browser and how it is cleaned up or garbage-collected. Garbage collection is handled automatically in the browser. It is the process in which the browser looks at all the objects that have been created. Objects that are no longer referenced are removed and the memory is reclaimed.

All browsers now have profiling tools built in. These will let you see which objects are using more memory over time.

Figure 4-6 shows the Profiles panel in Chrome 40.0.

A978-1-4302-6392-0_4_Fig6_HTML.jpg

Figure 4-6.

Profiles panel in Chrome 40.0

Figure 4-7 shows the equivalent panel in Firefox 35.0.1, the Performance tab.

A978-1-4302-6392-0_4_Fig7_HTML.jpg

Figure 4-7.

Profile Panel in Firefox 35.0.1

Using the profiler is similar in that you need the browser to record the application in action. In this case you take what’s called a snapshot. The Gmail team recommends taking three, in the following order:

1.

Take a snapshot.

2.

Perform the actions where you think the leak is coming from.

3.

Take the second snapshot.

4.

Perform the same actions.

5.

Take the third snapshot.

6.

Filter the objects from Snapshot 1 and 2 in the Summary view of Snapshot 3.

At this point you can start to see all the objects that are still around and could be taking up memory. You should now be able to see which references are remaining and dispose of them.

So what are references? Generally a reference happens when an object has a property whose value is another object. Listing 4-3 shows an example.

Listing 4-3. Creating Object References

var myObject = {};

myObject.property = document.createElement('div');

mainDiv.appendChild(myObject.property);

Here myObject.property now has a reference to the newly created div object. The appendChild method can use it with no problem. If at some point you remove that div from the DOM, myObject will still have a reference to the div and will not be garbage-collected. When objects no longer hold on to references, they are automatically garbage-collected.

One way to remove the reference is by using the delete keyword, as illustrated in Listing 4-4.

Listing 4-4. Deleting Object References

delete myObject.property;

Summary

As you can see, modern browsers have the tools to give you an environment that helps you fully understand your application. If you do see areas where you can make improvements, the timeline can show how much memory is being used over time. The debugger can help you see the values of your variables at any given time. Using the profiler can help you see where you are leaking memory and how you can fix it.