"Work Principles and Practice Browser" <08> call stack: Why JavaScript code will stack overflow?

In the last article, we talked about, when a piece of code is executed, JavaScript engine will first compile and create the execution context. But in the end it did not specify what kind of code is considered compliant.

So then we have to clear, the circumstances under which the code is considered "section of the" code, it will be compiled and executed before the execution context is created. Generally speaking, there are so three cases:

1. When the implementation of a global JavaScript code, compiles global code and create a global execution context, but also in the life cycle of the entire page, only a global execution context.

2. When you call a function when the function of the body of the code is compiled, and create a function execution context, under normal circumstances, after the function executes, the function execution context created will be destroyed.

3. When using the eval function, eval code will be compiled, and create the execution context.

 

Well, further understanding of the execution context, that this section, we will continue to carry on this basis, talk with the call stack . Learn the call stack at least the following three advantages:

1. Can help you understand how the JavaScript engine behind;

2. gives you the ability to debug JavaScript code;

3. To help you get the interview, because the interview process, call stack exit rate is also very high topic.

 

For example, when you write JavaScript code you may sometimes encounter an error stack overflow, as shown below:

 

Why such a mistake will happen? This relates to the contents of the call stack. You should know that there are many JavaScript function, often will call another function is a function of the situation, the call stack is a data structure used to manage the relationship between function calls . So talk clearly call stack, you're going to figure out the function calls and stack structure .

 

 

What is the function call

Function call is to run a function, specific use is to use the function name followed by a pair of parentheses. Let's look at a simple example code:

var a = 2
function add(){
var b = 10
return  a+b
}
add()

This code is very simple, first create an add function, and then in the bottom of the code in turn calls the function.

So here we will use this simple code to explain the course of the next function call.

Prior to the implementation of the function add (), JavaScript engine creates the code for the above global execution context, including the function and variable declarations, you can refer to the following chart:

As can be seen from the figure, the code in the global variables and functions are stored in the global context of the environment variables.

After the execution context is ready, they start to perform a global code when executed to add here, JavaScript judge this is a function call, then do the following:

1. First, the global execution context, taken add function code.

2. Next, add the code function compiled, and execution context and create the executable code of the function.

3. Finally, the execution code and outputs the result.

 

When executed to add a function, we have two execution context - the execution context of the global context and add functions.

That is when executing JavaScript, there may be multiple execution context, the JavaScript engine is how to manage the execution context it?

The answer is through something called the stack data structure to manage the. What is the stack it? It is how to manage the execution context it?

 

 

What is the stack

On the stack, so you can combine an apt example to understand, a single-lane one-way street, was blocked at one end while the other end of the entrance no reminders, after blocking it can only go after the car first came out, it when a single line of the block may be viewed as a stack of container , the car entered the single line operation is called push , the car back out of operation is called the stack .

Therefore, similar to the one end of the stack is blocked in a single line, the car is similar to the elements in the stack, the stack element satisfies LIFO characteristics. You can see the figure below:

 

 

 

What is the call stack of JavaScript

It is this structure JavaScript engine stack to manage the execution context. After the execution context is created, JavaScript engine will perform the context onto the stack, which is usually used to manage the execution context stack called the execution context stack, also known as the call stack .

In order to facilitate a better understanding of your call stack, sample code below we look at slightly more complex segments:

var a = 2
function add(b,c){
  return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return  a+result+d
}
addAll(3,6)

在上面这段代码中,你可以看到它是在 addAll 函数中调用了 add 函数,那在整个代码的执行过程中,调用栈是怎么变化的呢?

下面我们就一步步地分析在代码的执行过程中,调用栈的状态变化情况。

 

第一步,创建全局上下文,并将其压入栈底。如下图所示:

 

从图中你也可以看出,变量 a、函数 add 和 addAll 都保存到了全局上下文的变量环境对象中。

全局执行上下文压入到调用栈后,JavaScript 引擎便开始执行全局代码了。首先会执行 a=2 的赋值操作,执行该语句会将全局上下文变量环境中 a 的值设置为 2。设置后的全局上下文的状态如下图所示:

接下来,第二步是调用 addAll 函数。当调用该函数时,JavaScript 引擎会编译该函数,并为其创建一个执行上下文,最后还将该函数的执行上下文压入栈中,如下图所示:

addAll 函数的执行上下文创建好之后,便进入了函数代码的执行阶段了,这里先执行的是 d=10 的赋值操作,执行语句会将 addAll 函数执行上下文中的 d 由 undefined 变成了 10。

然后接着往下执行,第三步,当执行到 add 函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈,如下图所示:

当 add 函数返回时,该函数的执行上下文就会从栈顶弹出,并将 result 的值设置为 add 函数的返回值,也就是 9。如下图所示:

紧接着 addAll 执行最后一个相加操作后并返回,addAll 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文了。最终如下图所示:

 

至此,整个 JavaScript 流程执行结束了。

好了,现在你应该知道了调用栈是 JavaScript 引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系

 

 

在开发中,如何利用好调用栈

鉴于调用栈的重要性和实用性,那么接下来我们就一起来看看在实际工作中,应该如何查看和利用好调用栈。

 

1. 如何利用浏览器查看调用栈的信息

当你执行一段复杂的代码时,你可能很难从代码文件中分析其调用关系,这时候你可以在你想要查看的函数中加入断点,然后当执行到该函数时,就可以查看该函数的调用栈了。

这么说可能有点抽象,这里我们拿上面的那段代码做个演示,你可以打开“开发者工具”,点击“Source”标签,选择 JavaScript 代码的页面,然后在第 3 行加上断点,并刷新页面。你可以看到执行到 add 函数时,执行流程就暂停了,这时可以通过右边“call stack”来查看当前的调用栈的情况,如下图:

 

从图中可以看出,右边的“call stack”下面显示出来了函数的调用关系:栈的最底部是 anonymous,也就是全局的函数入口;中间是 addAll 函数;顶部是 add 函数。这就清晰地反映了函数的调用关系,所以在分析复杂结构代码,或者检查 Bug 时,调用栈都是非常有用的。

除了通过断点来查看调用栈,你还可以使用 console.trace() 来输出当前的函数调用关系,比如在示例代码中的 add 函数里面加上了 console.trace(),你就可以看到控制台输出的结果,如下图:

 

2. 栈溢出(Stack Overflow)

现在你知道了调用栈是一种用来管理执行上下文的数据结构,符合后进先出的规则。不过还有一点你要注意,调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript 引擎就会报错,我们把这种错误叫做栈溢出

特别是在你写递归代码的时候,就很容易出现栈溢出的情况。比如下面这段代码:

function division(a,b){
    return division(a,b)
}
console.log(division(1,2))

当执行时,就会抛出栈溢出错误,如下图:

从上图你可以看到,抛出的错误信息为:超过了最大栈调用大小(Maximum call stack size exceeded)。

那为什么会出现这个问题呢?这是因为当 JavaScript 引擎开始执行这段代码时,它首先调用函数 division,并创建执行上下文,压入栈中;然而,这个函数是递归的,并且没有任何终止条件,所以它会一直创建新的函数执行上下文,并反复将其压入栈中,但栈是有容量限制的,超过最大数量后就会出现栈溢出的错误

理解了栈溢出原因后,你就可以使用一些方法来避免或者解决栈溢出的问题,比如把递归调用的形式改造成其他形式,或者使用加入定时器的方法来把当前任务拆分为其他很多小任务。

 

 

总结

好了,今天的内容就讲到这里,下面来总结下今天的内容。

每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码;

如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶;

当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈;

当分配的调用栈空间被占满时,会引发“堆栈溢出”问题;

 

栈是一种非常重要的数据结构,不光应用在 JavaScript 语言中,其他的编程语言,如 C/C++、Java、Python 等语言,在执行过程中也都使用了栈来管理函数之间的调用关系。所以栈是非常基础且重要的知识点,你必须得掌握。

 

 

 

 

 

 


注: 本文出自极客时间(浏览器工作原理与实践),请大家多多支持李兵老师。如有侵权,请及时告知。

 

Guess you like

Origin www.cnblogs.com/zzd0916/p/11950363.html