JavaScript debugging

If you are not a completely new programmer, I am sure you must have spent some amount of time debugging your or someone else's code. Debugging is almost like an art form. Every language has different methods and challenges around the debugging. JavaScript is traditionally a difficult language to debug. I have spent days and nights in misery, trying to debug badly written JavaScript code using alert() functions. Fortunately, modern browsers, such as Mozilla, Firefox, and Google Chrome, have excellent Developer Tools to help debug JavaScript in the browser. There are IDEs like IntelliJ IDEA and WebStorm with great debugging support for JavaScript and Node.js. In this chapter, we will focus primarily on Google Chrome's built-in developer tool. Firefox also supports Firebug extension and has excellent built-in developer tools, but as they behave more or less the same as Google Chrome's Developer Tools, we will discuss common debugging approaches that work in both of these tools.

Before we talk about the specific debugging techniques, let's understand the type of errors we would be interested in while we try to debug our code.

Syntax errors

When your code has something that does not confirm to the JavaScript language grammar, the interpreter rejects that piece of code. These are easy to catch if your IDE is helping you with syntax checking. Most modern IDEs help with these errors. Earlier, we discussed the usefulness of tools, such as JSLint and JSHint, around catching syntax issues with your code. They analyze the code and flag errors in the syntax. The JSHint output can be very illuminating. For example, the following output shows up so many things we can change in the code. The following code snippet is from one of my existing projects:

    temp git:(dev_branch) X jshint test.js 
    test.js: line 1, col 1, Use the function form of "use strict". 
    test.js: line 4, col 1, 'destructuring expression' 
      is available in ES6 (use esnext option) or 
      Mozilla JS extensions (use moz). 
    test.js: line 44, col 70, 'arrow function syntax (=>)' 
      is only available in ES6 (use esnext option). 
    test.js: line 61, col 33, 'arrow function syntax (=>)'
      is only available in ES6 (use esnext option). 
    test.js: line 200, col 29, Expected ')' to match '(' from
      line 200 and instead saw ':'. 
    test.js: line 200, col 29, 'function closure expressions' 
      is only available in Mozilla JavaScript extensions (use moz option). 
    test.js: line 200, col 37, Expected '}' to match '{' from 
      line 36 and instead saw ')'. 
    test.js: line 200, col 39, Expected ')' and instead saw '{'. 
    test.js: line 200, col 40, Missing semicolon. 

Using strict

We briefly discussed the strict mode in earlier chapters. When you enable strict mode, JavaScript stops being accepting of syntactical errors in your code. Rather than silently failing, strict mode makes these failure throw errors instead. It also helps you convert mistakes into actual errors. There are two ways of enforcing strict mode. If you want the strict mode for the entire script, you can just add the use strict statement (with the quotes) as the first line of your JavaScript program. If you want a specific function to confirm to strict mode, you can add the directive as the first line of a function. For example, take a look at the following code snippet:

    function strictFn(){    
      // This line makes EVERYTHING under this scrict mode 
      'use strict';    
      ... 
      function nestedStrictFn() {  
        //Everything in this function is also nested 
        ... 
      }    
    } 

Runtime exceptions

These errors appear when you execute the code, try to refer to an undefined variable, or try to process a null. When a runtime exception occurs, any code after that particular line, which caused the exception, does not get executed. It is essential to correctly handle such exceptional scenarios in the code. While exception handling can help prevent crashes, they also aid in debugging. You can wrap the code that may encounter a runtime exception into a try{ } block. When any code inside this block generates a runtime exception, a corresponding handler captures it. The handler is defined by a catch(exception){} block. Let's clarify this using the following example:

    try { 
      var a = doesnotexist; // throws a runtime exception 
    } catch(e) {  
      console.log(e.message);  //handle the exception 
      //prints - "doesnotexist is not defined" 
    } 

In this example, the var a = doesnotexist line tries to assign an undefined variable, doesnotexist, to another variable a. This causes a runtime exception. When we wrap this problematic code into try{}catch(){} block or when the exception occurs (or is thrown), the execution stops in the try{} block and goes directly to the catch() {} handler. The catch handler is responsible for handling the exceptional scenario. In this case, we are displaying the error message on the console for debugging purposes. You can explicitly throw an exception to trigger an unhandled scenario in the code. Consider the following example:

    function engageGear(gear){ 
      if(gear==="R"){ console.log ("Reversing");} 
      if(gear==="D"){ console.log ("Driving");} 
      if(gear==="N"){ console.log ("Neutral/Parking");} 
      throw new Error("Invalid Gear State"); 
    } 
    try 
    { 
      engageGear("R");  //Reversing 
      engageGear("P");  //Invalid Gear State 
    } 
    catch(e){ 
      console.log(e.message); 
    } 

In this example, we are handling valid states of a gear shift: R, N, and D; however, when we receive an invalid state, we are explicitly throwing an exception clearly stating the reason. When we call the function which we think may throw an exception, we will wrap the code in the try{} block and attach a catch(){} handler with it. When the exception is caught by the catch() block, we will handle the exceptional condition appropriately.

Console.log and asserts

Displaying the state of execution on console can be very useful while debugging. Although, modern developer tools allow you to put breakpoints and halt execution to inspect a particular value during runtime. You can quickly detect small issues by logging some variable state on the console.

With these concepts with us, let's see how we can use Chrome Developer Tools to debug JavaScript code.

Chrome Developer Tools

You can start Chrome Developer Tools by clicking menu | More tools | Developer Tools. Take a look at the following screenshot:

Chrome Developer Tools

Chrome developer tool opens up on the lower pane of your browser and has a bunch of very useful sections. Consider the following screenshot:

Chrome Developer Tools

The Elements panel helps you inspect and monitor the DOM tree and associated style sheet for each of these components.

The Network panel is useful to understand network activity. For example, you can monitor the resources being downloaded over the network in real time.

The most important pane for us is the Sources pane. This pane is where the JavaScript and the debugger are displayed. Let's create a sample HTML with the following content:

    <!DOCTYPE html> 
    <html> 
    <head> 
      <meta charset="utf-8"> 
      <title>This test</title> 
      <script type="text/javascript"> 
      function engageGear(gear){ 
        if(gear==="R"){ console.log ("Reversing");} 
        if(gear==="D"){ console.log ("Driving");} 
        if(gear==="N"){ console.log ("Neutral/Parking");} 
        throw new Error("Invalid Gear State"); 
      } 
      try 
      { 
        engageGear("R");  //Reversing 
        engageGear("P");  //Invalid Gear State 
      } 
      catch(e){ 
        console.log(e.message); 
      } 
      </script> 
    </head> 
    <body> 
    </body> 
    </html> 

Save this HTML file and open it in Google Chrome. Open Developer Tools in the browser, and you will see the following screen:

Chrome Developer Tools

This is the view of the Sources panel. You can see the HTML and embedded JavaScript source in this panel. You can see the Console window as well, and you can see that the file is executed and the output is displayed on console.

On the right side, you will see the debugger window, as shown in the following screenshot:

Chrome Developer Tools

In the Sources panel, click on the line numbers 8 and 15 to add a breakpoint. The breakpoints allow you to stop execution of the script at the specified point. Consider the following screenshot:

Chrome Developer Tools

In the debug pane, you can see all existing breakpoints. Take a look at the following screenshot:

Chrome Developer Tools

Now, when you rerun the same page, you will see that the execution stops at the debug point. Consider the following screenshot:

Chrome Developer Tools

This window now has all the action. You can see that the execution is paused on line 15. In the debug window, you can see which breakpoint is being triggered. You can also see the Call Stack and resume execution in several ways. The debug command window has a bunch of actions. Take a look at the following screenshot:

Chrome Developer Tools

You can resume execution, which will execute until the next breakpoint, by clicking on the following button:

Chrome Developer Tools

When you do that, the execution continues until the next breakpoint is encountered. In our case, we will halt at line 8. Consider the following screenshot:

Chrome Developer Tools

You can observe that the Call Stack window shows how we arrived at line 8. The Scope panel shows the Local scope where you can see the variables in the scope when the breakpoint was arrived at. You can also Step-Into or Step-over the next function.

There are other very useful mechanisms to debug and profile your code using Chrome Developer Tools. I would suggest you to experiment with the tool and make it a part of your regular development flow.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.198.43