JavaScript 调用堆栈如何工作是每个前端开发人员在其职业生涯中至少问过一次的问题,在我看来,这不是一个大多数地方都得到了解答,但答案并不总是清晰或易于理解。这就是为什么我决定在这篇文章中讨论这个主题。
让我们从头开始吧。 JavaScript 引擎同步逐行运行代码,每次执行函数时,它都会创建一个执行上下文(内存中的空间来存储所有仅存在的作用域属性在该函数内)并将该函数添加到调用堆栈。
JavaScript 只执行栈顶函数的代码,当函数完成并返回其值时,引擎从调用堆栈中删除该函数 并开始处理下一个。
当调用堆栈为空时,JavaScript引擎继续在下一个全局上下文中运行代码,或者相同的,继续执行JavaScript 文件的根目录 并且不属于任何函数。
让我们逐行看一些示例:
const num1 = 2; const num2 = 5; function sum(a, b){ return a + b; } const result= sum(num1, num2); console.log(result) // 7
sum 对 2 个数字求和并返回总和的结果。最后,创建常量 result,并将使用参数 num1 和 num2 执行 sum 的结果分配给它。然后结果的值会打印在控制台上。 如果您认为前面的解释太简单或太复杂并且没有解释任何内容,
请耐心等待,我们正在讲有趣的事情。 让我们看看 JavaScript 引擎正在做什么,
逐行。在第一行中,引擎创建一个标签 num1 并将 存储在内存中 值 2.
const num1 = 2;
执行相同的操作。它创建一个标签 num2 并将值 5. 存储在内存中
const num2 = 5;
内的人需要num2的值时,引擎就会更改标签并输入值5 改为 。 让我们继续下一行。下一行是
函数 sum。你认为引擎会做什么?你认为它会执行该函数还是将其添加到调用堆栈中? 不!引擎要做的是存储一个名为sum的新标签并将括号内的代码存储在内存中。或者相同的是,它将存储函数定义并将其分配给sum标签。
function sum(a, b){ return a + b; }
,我们会看到如下所示的内容:
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
有趣的一行。当 JavaScript 引擎到达下一行时,它会创建标签 result,但此时,它还不知道需要为标签分配什么值,因为 值是执行函数的结果,所以首先需要执行函数,做函数需要做的事情,然后从返回值中获取结果。
const result= sum(num1, num2);
,并创建一个新的执行上下文,这是一个新的内存空间 JavaScript 可以存储 args 的值以及函数内部的所有其他属性,而不会与全局上下文发生冲突。
First of all, the engine creates in memory the labels a and b which are the names given to the parameters, and it assigns the value of the arguments 2 and 5 respectively.
If we could see the memory at this specific moment, we would see something like this:
Label name | Value in memory |
---|---|
a | 2 |
b | 5 |
return | 2 + 5 = 7 |
In this case, the function is really simple and only returns the value of the sum between a and b, so the engine substitutes the parameters with the values of the arguments and returns the value to the global execution context. Finally, the function is removed from the call stack, and only the global context remains.
Call stack |
---|
Global |
At this point, the result of the function is assigned to the label result and we can print the value on console with the console log.
Let's take a look at how the global memory looks now:
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
result | 7 |
Did you noticed? the label result has a value of 7? and also sum still has the function definition inside.
Let's take a look at the next code:
const num1 = 2; const num2 = 5; function sum(a, b){ return a + b; } const result= sum(num1, num2); console.log(result) // 7 function sumThreeNumbers = (x,y,z) => { return sum(x, y) + z } const result2 = sumThreeNumbers(4,6,2) console.log(result2) // 12
The main difference is that now we have a new sumThreeNumbers function and we are creating a new result2 constant and assigning the value of running the function sumThreeNumbers with the arguments, 4, 6 and 2.
Let's take a look at how the call stack works when we run nested functions.
If we jump to the line when we define the constant result2 the global memory would look something like this:
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
result | 7 |
sumThreeNumbers | function definition |
Just as on the previous example, the JavaScript engine doesn't know what value to assign to the label result2, to get the value, first needs to execute the function sumThreeNumbers with the arguments. The function is added to the call stack, and a new execution context is created. The execution context would look like this:
Label name | Value in memory |
---|---|
x | 4 |
y | 6 |
z | 2 |
So the first thing that JavaScript does is create the parameter labels and assign the value provided by the arguments.
Now let's take a look at our call stack
Call stack |
---|
sumThreeNumbers |
Global |
As you can see, the call stack only has the sumThreeNumbers item (apart from the global context that is always present).
To be able to get the result value, the function sum needs to be executed first, so the engine will add the function to the call stack and create a new execution context for the sum function.
Call stack |
---|
sum |
sumThreeNumbers |
Global |
As the sum function is on top of the call stack, Javascript needs to run sum first to be able to continue running sumThreeNumbers.
This is how it's going to look the execution context for the function sum:
Label name | Value in memory |
---|---|
a | 4 |
b | 6 |
return | 4 + 6 = 10 |
如你所知,它将返回 _10 _并且将从调用堆栈中删除
Call stack |
---|
sumThreeNumbers |
Global |
JavaScript 将继续执行 sumThreeNumbers 并且执行上下文将如下所示:
Label name | Value in memory |
---|---|
x | 4 |
y | 6 |
z | 2 |
return | 10 + 2 = 12 |
它将返回值12并从调用堆栈中删除。
Call stack |
---|
Global |
然后值12将被分配给属性result2并且值12将显示在控制台中。
我希望这篇文章能帮助您理解JavaScript 调用堆栈的工作原理,如果是的话请留下评论和点赞。我在下一篇中见到你!
以上是揭秘 JavaScript 调用堆栈:代码的实际运行方式的详细内容。更多信息请关注PHP中文网其他相关文章!