简单理解JavaScript中的执行上下文和堆栈
什么是执行上下文?
JavaScript的执行环境非常重要。当JavaScript代码在一行中时,它将被预处理为以下情况之一:
全局代码-首次执行代码的默认环境。函数代码-每当执行流进入函数体时。Eval代码-要在eval函数中执行的文本。你可以阅读很多关于范围的在线材料,但是为了使事情更容易理解,让我们将术语“执行上下文”作为当前代码的运行环境或范围。接下来,让我们看一个具有全局和函数/局部上下文的代码示例。
这里没什么特别的。我们有一个由紫色边框表示的全局上下文,以及三个由绿色、蓝色和橙色边框表示的不同功能上下文。只能有一个全局上下文,可以从程序中的任何其他上下文访问。
您可以有任意数量的函数上下文,每个函数调用都会创建一个新的上下文,从而创建一个私有范围,在该范围内,不能从当前函数范围之外直接访问函数内部声明的任何内容。在上面的示例中,函数可以访问在其当前上下文之外声明的变量,但是外部上下文不能访问在其中声明的变量或函数。为什么会这样?这个代码是如何处理的?
执行上下文堆栈(执行上下文堆栈)
浏览器中的JavaScript解释器是作为单线程实现的。事实上,这意味着在浏览器中一次只能做一件事,其他动作或事件在所谓的执行堆栈中排队。下图是单线程堆栈的抽象视图:
我们已经知道,当浏览器第一次加载脚本时,默认情况下,它会进入全局上下文进行执行。如果在全局代码中调用一个函数,程序的序列流进入被调用的函数,创建一个新的执行上下文,并将其推到执行栈的顶部。
如果在当前函数中调用另一个函数,也会发生同样的事情。代码的执行流进入内部函数,该函数创建一个新的执行上下文,该上下文被推到现有堆栈的顶部。浏览器将始终在堆栈顶部执行当前执行上下文,一旦函数完成当前执行上下文的执行,它将从堆栈顶部弹出,并将控制返回到当前堆栈中的下一个上下文。以下示例显示了递归函数和程序的执行堆栈:
(function foo(I){ if(I===3){ return;} else { foo(I);}}(0));
代码简单地调用自己3次,并将I的值增加1。每次调用函数foo时,都会创建一个新的执行上下文。一旦上下文完成执行,它就会弹出堆栈,并将控制权返回给它下面的上下文,直到它再次到达全局上下文。关于执行栈执行栈有五个要点:
单线程。同步执行。全球背景。任意数量的函数上下文。每个函数调用都会创建一个新的执行上下文,甚至是对自身的调用。执行上下文的详细信息
所以我们现在知道,每次调用一个函数,都会创建一个新的执行上下文。然而,在JavaScript解释器中,对执行上下文的每次调用都有两个阶段:
1.创建阶段[在调用函数时,但在执行任何代码之前]:
创建范围链。创建变量、函数和参数。确定“这个”的价值。2.激活/代码执行阶段:
赋值、引用函数和解释/执行代码。每个执行上下文可以在概念上表示为具有三个属性的对象:
executioncontextobj={ ' scope chain ' : {/*所有父执行上下文的variable object */},' variableobject' : {/*函数参数/形式参数、内部变量和函数声明*/},' this': {}}激活对象/变量对象[ao/]
executionContextObj是在调用并实际执行函数之前创建的。这叫第一阶段,也就是创作阶段。此时,解释器通过扫描函数传递的实际参数或形式参数、局部函数声明和局部变量声明来创建executionContextObj。此扫描的结果将成为executionContextObj中的变量对象。
以下是解释器如何预处理代码的伪代码概述:
1.找一些代码来调用函数。
2.在执行函数代码之前,创建执行上下文。
3.进入创建阶段:
初始化范围链。创建变量对象:创建参数对象,检查参数的上下文,初始化名称和值,并创建引用副本。扫描函数声明的上下文:对于找到的每个函数,在变量对象中创建一个属性,它是函数的确切名称,并且在内存中有一个指向该函数的引用指针。如果函数名已经存在,引用指针值将被覆盖。扫描变量声明的上下文:对于找到的每个变量声明,在变量对象中创建一个属性作为变量名,并将值初始化为undefined。如果变量名已经存在于变量对象中,不做任何操作并继续扫描。确定上下文中“这个”的值。4.激活/执行阶段:
在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。让我们看一个例子:
函数foo(I){ var a=' hello ';var b=函数privateB(){ };函数c(){ } } foo(22);当调用foo(22)时,创建阶段如下:
foexecutioncontext={ scope chain : }.},variable object : { arguments : { 0: 22,length: 1},i: 22,C:指向函数c()的指针a: undefined,b: undefined},this3360 {.}}如您所见,创建阶段处理的是定义属性的名称,而不是为其赋值,除了正式的形式参数/实际参数。创建阶段完成后,执行流程进入函数,函数执行后激活/代码执行阶段如下:
foexecutioncontext={ scope chain : }.},variable object : { arguments : {0: 22,length: 1},i: 22,C:指向函数C()的指针A:' Hello ',B:指向函数Privateb ()}的指针,这: { 0.}}关于吊装
您可以找到许多使用JavaScript定义术语提升的在线资源,解释变量和函数声明提升到了它们的函数范围的顶部。然而,没有人能详细解释为什么会发生这种情况,并且很容易理解为什么,因为有了关于解释器如何创建活动对象的新知识。请看下面的代码示例:
(function(){ console . log(foo的类型);//function pointer console . log(bar的类型);//undefinedvar foo='hello ',bar=function(){ return ' world ';};function foo(){返回“hello”;}}());我们现在能回答的问题是:
为什么我们可以在宣布foo之前参观?如果我们理解创建阶段,我们就知道变量是在激活/代码执行阶段之前创建的。因此,当函数流开始执行时,foo已经在激活对象中定义了。Foo声明了两次,为什么foo显示为函数而不是undefined或string?即使foo声明了两次,我们通过创建阶段知道函数是在变量之前在活动对象上创建的,如果属性名已经存在于活动对象上,我们只需绕过声明步骤。因此,首先在活动对象上创建对function foo()的引用,当解释器到达var foo时,我们已经看到属性名foo存在,所以代码什么也不做,继续处理。为什么bar是未定义的?Bar实际上是一个带有函数赋值的变量。我们知道变量是在创建阶段创建的,但是它们是用未定义的值初始化的。摘要
希望你已经掌握了JavaScript解释器如何对你的代码进行预处理。了解执行上下文和堆栈可以帮助您理解其背后的原因:为什么预处理值与您预期的不同。
你认为学习口译员的内部工作原理有必要还是没有必要?知道执行上下文阶段能帮助你写更好的JavaScript吗?
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:简单理解JavaScript中的执行上下文和堆栈是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。