当前位置: 首页 > Web前端 > HTML5

#JavaScript中执行上下文和队列(栈)是什么关系?

时间:2023-04-04 23:41:04 HTML5

原文:JavaScript中的ExecutionContext&Stack是什么?git地址:JavaScript中执行上下文和队列(栈)的关系?导读:以前总看到相关文章提到变量提升,函数提升,还有什么函数提升的优先级高于变量。,和var赋值,然后让你告诉他每个阶段应该取什么值,用变量提升和函数提升是没有意义的,至少我不会-_-。DavidShariff的这篇文章告诉我们它是如何工作的,而且很简单。在本文中,我将深入研究JavaScript最基本的部分之一,即执行上下文。在本文结束时,您将更好地了解解释器做了什么以便某些函数和变量可以在声明之前使用,以及它们的值是如何确定的。什么是执行上下文?代码在JavaScript中运行时,其执行环境非常重要,分为以下几类:全局代码--第一次执行代码的默认环境函数代码--每次执行流进入函数体时Evalcode--to内部eval函数中执行的文本为了便于理解,本文中的执行上下文是指:当前执行代码的环境和范围;接下来,让我们看一段执行上下文中包含全局和函数内容的代码:这里没有什么特别的,一个全局上下文用紫色边框表示,三个不同的函数上下文用绿色、蓝色和橙色边框表示.只能有1个全局上下文,可以从程序中的任何其他上下文访问。您可以拥有任意数量的函数上下文,并且每个函数调用都会创建一个新的上下文,从而创建一个私有范围,在该范围内,函数内部声明的任何内容都无法从当前函数范围外部直接访问。在上面的示例中,函数可以访问在其当前上下文之外声明的变量,但外部上下文不能访问在其内部声明的变量/函数。为什么?这段代码究竟是如何工作的?执行上下文堆栈浏览器中的JavaScript解释器以单线程方式运行。这意味着浏览器一次只执行一件事,其他事件在执行队列中排队。下图是单线程队列的抽象视图:我们已经知道,当浏览器第一次加载你的脚本时,默认是全局执行上下文(globalexecutioncontent)。如果你在你的全局代码中调用一个函数,程序的序列流进入被调用的函数,创建一个新的函数执行上下文并将该上下文推到顶部执行堆栈(执行队列)。如果在当前函数中调用另一个函数,也会发生同样的事情。代码的执行流程进入内部函数,内部函数创建一个执行上下文并将其推到执行队列的顶部。浏览器总是在栈顶执行执行上下文,一旦函数执行完当前执行上下文,它就会从栈顶弹出,将控制权返回给当前栈中的下层上下文。以下示例显示了递归函数和程序执行堆栈:(functionfoo(i){if(i===3){return;}else{foo(++i);}}(0));代码只调用自身3次,将i的值递增1。每次调用foo函数时,都会创建一个新的执行上下文。一旦执行完成,它会弹出栈,将控制权交给它下面的上下文,直到再次到达全局上下文(你想到koa2的洋葱图了吗?)这里是执行队列的5个关键点:single-线程,同步执行全局上下文无限级别的函数上下文每个函数调用都会创建一个新的执行上下文,包括对其自身的调用(递归)执行上下文详细信息因此我们现在知道每次调用一个函数都会创建一个新的执行上下文(执行语境)。但是,在JavaScript解释器中,每次调用都会有两个阶段来生成执行上下文:创建阶段[当调用函数时,但在执行任何代码之前]:作用域链的创建。创建变量、函数和参数以确定“this”。激活/执行阶段:var赋值,(函数声明)指向函数,解释/执行代码可以在概念上将每个执行上下文表示为一个具有3个属性的对象:executionContextObj={'scopeChain':{/*variableObject+allparentexecutioncontext'svariableObject*/},'variableObject':{/*functionarguments/parameters,innervariableandfunctiondeclarations*/},'this':{}}activation/variableobject[AO/VO]这个executionContextObj是调用函数,但是在执行实际功能之前。这是第一阶段:创造阶段。在这里,解释器通过扫描传入的参数或参数、局部函数声明和局部变量声明来创建一个executionContextObj。此扫描的结果成为executionContextObj.variableObject。以下是解释器如何解析代码的伪概述:遇到函数调用。在执行函数代码之前,创建一个执行上下文(executioncontext)。进入创建阶段:初始化作用域链(ScopeChain)。创建一个变量对象(variableobject):创建一个参数对象,检查参数的上下文,初始化名称和值并创建引用的副本。扫描函数声明的上下文:对于每一个找到的函数,在变量对象中创建一个以函数名为属性的键值对,值指向内存中指向该函数的引用指针。如果函数名已经存在,引用指针值将被覆盖。扫描变量声明的上下文:对于找到的每个变量声明,在变量对象中创建一个具有变量名称属性的键值对,并将值初始化为未定义。如果变量名已经存在于变量对象中,什么也不做,继续扫描。确定上下文中“this”的值。激活/执行阶段:在上下文中运行/解析函数体的代码,并在代码逐行执行时为变量赋值。让我们看一个例子:functionfoo(i){vara='hello';varb=functionprivateB(){};函数c(){}}foo(22);当调用foo(22)时,创建阶段如下所示:fooExecutionContext={scopeChain:{...},variableObject:{arguments:{0:22,length:1},i:22,c:pointertofunctionc()a:undefined,b:undefined},this:{...}}可以看到,创建阶段定义了属性的名称,并没有给它们赋值,除了形参/形参(函数传递参数、参数)。创建阶段完成后,执行流程进入函数体,函数执行完成后的执行阶段如下:fooExecutionContext={scopeChain:{...},variableObject:{arguments:{0:22,length:1},i:22,c:pointertofunctionc()a:'hello',b:pointertofunctionprivateB()},this:{...}}提升在很多JavaScript资料中都有提到,解释变量和函数声明被提升到它们作用域的顶部。但是,没有人详细解释为什么会发生这种情况,一旦您掌握了解释器如何创建激活对象,就很容易理解。示例:(function(){console.log(typeoffoo);//函数指针console.log(typeofbar);//undefinedvarfoo='hello',bar=function(){return'world';};functionfoo(){return'hello';}}());我们现在可以回答的问题是:为什么我们可以在声明foo之前访问它?如果我们遵循创建阶段,我们就会知道变量是在激活/代码执行阶段之前创建的。所以当功能流程开始执行时,激活对象中已经定义了foo。foo被声明了两次,为什么foo显示为函数,__不是__undefined或string?即使foo被声明了两次,我们也知道创建阶段函数是在变量之前的激活对象上创建的。如果激活对象中已经存在属性名称,解释器将忽略此声明。因此,首先将在激活对象上创建对foo()的引用,当解释器到达varfoo时,属性名称foo存在,因此代码什么都不做并继续。为什么酒吧未定义?bar其实是一个带函数赋值的变量,我们知道变量是在创建阶段创建的,但是它们的初值是undefined。总结希望现在您已经很好地掌握了JavaScript解释器如何执行您的代码。了解执行上下文和队列可以让您深入了解为什么您的代码没有按预期运行您是否认为了解解释器的内部工作是您的JavaScript知识的重要组成部分?了解执行上下文的每个阶段是否有助于您编写更好的JavaScript?__NOTE__:一直有人问闭包、回调、超时等,我将在下一篇文章中介绍,主要概述作用域链和执行上下文的关系。详细了解ECMA-262-3。第2章可变对象标识符解析、执行上下文和作用域链