Use debug in Chrome developer tools to observe function call stacks, scope chains, and closures. Only with this knowledge can you capture the data you want.

In Chrome's developer tools, through breakpoint debugging, we can very conveniently observe the detailed changes in the execution of the JavaScript code step by step. We can intuitively perceive the changing process of key information such as function call stacks, variable objects, scope chains, closures, and this. Therefore, breakpoint debugging plays a very important role in quickly locating code errors and understanding the code execution process. This is also an essential advanced skill for our front-end development.

Review of basic concepts

When a function is called for execution, it creates an execution context for the current function. During the creation phase of the execution context, variable objects, scope chains, closures, this, etc. will be confirmed separately. Generally speaking, there will be multiple functions executed in a program, so the execution engine will use the function call stack to manage the execution order of these functions. The execution order of the function call stack is consistent with the stack data structure.

Get to know breakpoint debugging tools

Try to bring up the developer tools of the Chrome browser in the latest version of the Chrome browser (there is no guarantee that the old version of the Chrome browser will be the same as mine). And open the following interface according to the following sequence.

The three vertical dots in the upper right corner of the browser -> More Tools -> Developer Tools -> Sources

In order to let everyone understand more clearly where we need to focus on, I marked them with arrows.

The area pointed by the arrow on the left represents the number of lines of code. When we click on a certain line, we can set a breakpoint on that line.

There is a row of icons in the area pointed by the first arrow on the right. We can control the execution process of the function through this row of icons. From left to right they are:

  • **resume/pause script execution**
    Resume/pause script execution

  • **step over next function call**
    Cross over. The actual representation is that when no function is encountered, the next step is executed. When a function is encountered, the next step is executed directly without entering the function.

  • step into next function call
    Step into. The actual performance is that when no function is encountered, the next step is executed. When a function is encountered, the function execution context is entered.

  • step out of current function
    Jump out of current function

  • deactivate breakpoints
    Disable breakpoints

  • don‘t pause on exceptions
    Do not pause exception catching

Among them, step over, step in, and jump out are the three most commonly used operations during debugging.

The area pointed to by the second arrow on the right in the above figure is the Call Stack (the function call stack of the current execution context).

The area pointed to by the third arrow on the right in the above figure is Scope. That is the scope chain of the current function. Which Localrepresents the currently executing active object and Closurerepresents the closure.

Therefore, we can use the display of the scope chain here to observe who the closure is, which is very helpful for an in-depth understanding of closures.

Click on the area that displays the number of lines of code to set a breakpoint at the corresponding line of code. After setting a breakpoint and refreshing the page, the code will be executed and paused at the breakpoint location. At this time, we can debug our code step by step through the several icon operations introduced above.

In Chrome, breakpoints cannot be set on the line between separate variable declaration (without assignment operation) and function declaration.

Example

Next, in order to further master breakpoint debugging, we will use some examples. Use breakpoint debugging to observe the execution process of these codes. Here we mainly use examples of closures.

// demo01
var fn;
function foo() {
    var a = 2;
    function baz() {
        console.log( a );
    }
    fn = baz;
}
function bar() {
    fn();
}

foo();
bar(); // 2

We can first think about how the closure is generated in this example. Then use debugging to verify whether your idea is correct.

Obviously, fn is in foo or a reference to foo's internal function baz. So when fn is executed, it is actually baz execution. And baz accessed the variables in foo during execution, so the closure was generated. In Chrome, foo is used to refer to the generated closure.

Step 1: Set a breakpoint and refresh the page.

After a simple analysis, it can be seen that the code starts executing from the foo() line, so set a breakpoint here.

Step 2: Click the icon (step into) pointed by the arrow in the picture above. The function of this button will be executed step by step according to the execution order of the code. When a function is encountered, it will jump into the function execution. Click multiple times until the baz function is executed, as shown in the figure:

We should pay attention to the changes in various variables during code execution, as well as the changes in Call Stack and Scope. When the baz function is executed, the Call Stack and Scope are as shown in the figure above. If you have enough understanding of the previous knowledge, you should understand that this is the difference between function call stacks. The scope chain will not change due to closures.

Another point we need to pay attention to is an optimization made by chrome for scope chain.

There is a description of closures in "JavaScript Advanced Programming 3", "A closure saves the entire variable object, not a special variable."

In the above example, we can clearly know that in the variable object of function foo, there should be a variable a and a function baz stored. But from the picture we can see that Closure(foo)there is no function baz, only the variable a.

In fact, this is an optimization made by the new version of Chrome for closures and scope chains. It only retains variables that will be accessed. We can further demonstrate this with the following example.

// demo02
function foo() {
    var x = 20;
    var y = 10;
    function child() {
        var m = 5;

        return function add() {
            var z = 'this is add';
            return x + y;
        }
    }

    return child();
}

foo()();

In the above example, we use the knowledge we learned previously to think about what the scope chain of the function add should look like when it is executed?

You can figure it out in a few seconds, right?

addEC = {
    scopeChain: [AO(add), VO(child), VO(foo), VO(Global)]
}

But what is the performance in chrome? Let's take a look.

Huh! The middle ones VO(child)are directly omitted. This is exactly the optimization of chrome. Because the variable m and function add in the child function are not accessed in the execution context of add, there is no need to keep them in memory.

The same example is debugged in Firefox, and it has clearly pointed out the variables and functions that have been optimized. As shown below. But through comparison, we found that chrome's optimization actually goes further.

In Firefox, only the variable objects that are not accessed are optimized, and among the variable objects that are accessed, the variables that are not accessed are not optimized. In Chrome, unaccessed variables are also optimized.

What needs to be noted here is the arguments object. In Chrome, the parameters are expanded and displayed in the variable object, while in Firefox, an arguments object is saved directly. The specific distinction can be made through the previous examples and diagrams.

Next let's look at a special example.

// demo03
function foo() {
    var a = 10;

    function fn1() {
        console.log(a);
    }

    function fn2() {
        var b = 10;
        console.log(b);
    }

    fn2();
}

foo();

Let's take a few seconds to think about it. When function fn2 is executed, is there any closure generated in this example?

As can be seen from the figure, closure is indeed generated.

In the latest MDN, closure is defined as follows (combined with the above example): "A closure refers to a scope (foo) that contains a function (fn1). This function (fn1) can be called by this scope. The enclosed variable (a), function, or closure, etc. Usually we obtain access to the closure through the function corresponding to the closure."

The definition here actually covers this special case. fn1 and foo jointly generate a closure, but fn1 has no place to call it. In actual development we almost never use it this way. So if you are asked in an interview just know how to answer.

Guess you like

Origin blog.csdn.net/weixin_44786530/article/details/135438749