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

【JS基础系列】吃透执行上下文和调用栈

时间:2023-04-05 13:04:28 HTML5

今天是系列的第六篇,主要讲讲执行上下文和调用栈的一些知识点。我们在之前的第二篇文章中讲到了作用域的一些知识。那篇文章提到还有执行上下文、变量提升等知识点。由于篇幅问题,我们没有解释清楚。今天的文章就是对这些知识进行补充整理。上一篇文章提到,js中的作用域是词法作用域(静态作用域),也就是说我们在写代码的时候,已经确定了变量和函数声明的位置,不会再改变。.之所以在这里重新回顾一下,是为了区分作用域和后续的执行上下文。请记住:词法范围是在编写代码时确定的,而我们后面要讲的执行上下文是在调用函数时创建的。这两者需要区分。首先,让我们从变量提升开始。让我们以变量提升为例:abs()//111functionabs(){console.log(111)}abs()//111varabs=function(){console.log(222)}abs()//222上面代码打印出来的值分别是111、111、222。上面代码的执行顺序其实是这样的:functionabs(){console.log(111)}varabs=undefinedabs()abs()abs=function(){console.log(222)}abs()我们可以看到原来打印值为111的函数是函数声明的方式;打印值为222的函数是函数表达式的方式。函数声明的提升优先级高于函数表达式,函数表达式的提升过程可以看作是变量提升赋值的过程。另外,要记住一点:当函数声明和函数表达式在同一个作用域内同名时,函数声明的提升会在前面,后面的同名变量不会覆盖赋值前面的函数声明。那么我们可以从这个例子中得出两个结论:函数声明的提升优先级高于函数表达式和变量。函数声明的提升促进了函数的创建、初始化和赋值;而函数表达式和变量的提升Promotion是提升变量的创建和初始化,不提升赋值。但实际上,函数执行的具体过程可能更复杂,接下来我们引入执行上下文和执行栈的概念。执行上下文和执行堆栈都是在函数执行时生成的。执行上下文定义它。它是JavaScript执行一段代码的运行时环境。执行上下文是在代码执行时创建的,它包括变量环境和词法环境。变量环境保存的是通过变量提升的函数和变量,词法环境保存的是块作用域内的变量。怎么理解呢?举个简单的例子:vara=10console.log(a)我们都知道最后会打印10,但是它的整个执行过程是这样的。在js引擎开始进入代码之前,进入编译阶段。此时创建了全局上下文和可执行代码。可执行代码是变量提升以外的代码。编译完成后,提升变量环境中的变量a,值为undefined,向下执行代码(即执行可执行代码)。执行完vara=10语句后,变量environment中的a被赋值10,用户进入控制台。log(a)的时候,js引擎在变量环境中寻找变量a,找到了,发现值为10,所以输出10其实有执行栈的概念。接下来我们让这个例子稍微复杂一点。解释什么是执行栈。执行堆栈定义它。它是一种用于管理执行上下文的数据结构。我们上面的例子是只有一个执行上下文的情况,分析起来比较简单。如果我们在一段代码中有100或1000个执行上下文怎么办?搞砸了吗?这时,我们引入了执行栈(一种数据结构)来管理这些执行上下文。举个例子对比一下执行栈和执行上下文:小明的妈妈给了小明10个苹果(执行上下文),小明装不下那么多,就找了一个jar(执行栈)来装这些apples,他把这些苹果一个一个装进罐子里(进stack),然后他把苹果拿出来野餐,到了野餐的地方,他把之前打包好的苹果拿出来(outofstack),取出的规则是后放入的先取出(栈的后进先出规则),所以jar是空的。看完上面小明的例子,你是不是有了一些基本的了解呢?OK,我们用一个稍微复杂一点的例子来分析这段代码做了什么。vara=10functioninner(){console.log(a)}functionrun(){vara=1inner()}run()有点复杂,我们一步步来看。首先我们来看一下词法作用域部分。词法作用域是在编写代码时确定的,是静态的。我们看到这段代码中有全局作用域、函数内部作用域和函数运行作用域。我们在第二篇文章中了解了作用域链,对吧?那么我们必须知道inner和run函数的作用域和上级作用域是什么。接下来是代码执行阶段。在进入代码前创建执行栈和全局执行上下文,并将全局执行上下文压入栈底,创建全局执行上下文中的变量环境和词法环境。我们在本文中的示例不介绍块级作用域。词法环境中间是空的,所以下面暂时忽略词法环境的细节。此时全局执行上下文中的变量环境中存在变量inner=function(){##}、run=function(){##}、a=undefined;当代码执行到vara=10时,变量环境a=10;然后执行run()创建函数run的函数执行上下文,并将执行上下文压栈(在全局执行上下文之上),创建函数的函数执行上下文的变量环境和词法环境跑步。当函数运行执行上下文的变量环境有a=undefined时,代码一直执行到vara=1,变量环境a=1;然后执行inner()创建函数inner的函数执行上下文,并将执行上下文压入栈中(在函数run的函数执行上下文上),创建函数执行上下文的变量环境和词法环境函数内部,此时函数运行执行上下文变量环境为空,代码执行console.log(a)。去当前作用域找变量a,发现a在当前作用域不存在,就去上层作用域,也就是全局作用域找变量a,发现a=10,所以它输出10.函数inner执行最后,函数inner的执行上下文从调用堆栈中移除。函数run的执行完成,函数run的执行上下文从调用栈中移除。这个时候,执行栈中还有一个全局的执行上下文。如果代码一直运行,全局执行上下文会一直存在于执行栈中;如果程序退出,全局执行上下文和调用栈将被销毁。上面的过程是不是很像小明把苹果放进罐子里再拿出来的过程?好了,今天的文章就到这里,我们来回顾一下今天的知识点。综上所述,作用域在写代码的时候就确定了,执行上下文在代码执行的时候就确定了。找出两者之间的区别。代码执行包括编译和执行两部分。编译阶段创建执行上下文和可执行代码;执行阶段执行可执行代码。执行上下文包括变量环境和词法环境。变量环境存储变量提升变量和函数,词法环境存储块范围内的变量。调用栈是一种用于管理执行上下文的数据结构。调用堆栈之于执行上下文就像罐子之于苹果。