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

了解Javascript执行上下文和执行栈

时间:2023-04-04 23:37:44 HTML5

如果你是一名JavaScript开发者,或者想成为一名JavaScript开发者,那么你必须了解JavaScript程序的内部执行机制。了解执行上下文和执行堆栈还有助于理解其他JavaScript概念,例如提升、作用域和闭包。正确理解执行上下文和执行堆栈的概念将帮助您成为更好的JavaScript开发人员。废话少说,言归正传。什么是执行上下文?简而言之,执行上下文是当前JavaScript代码解析和执行环境的抽象概念。在JavaScript中运行的任何代码都在执行上下文中运行。执行上下文的类型执行上下文分为三种类型:全局执行上下文:这是默认的和最基本的执行上下文。不在任何函数中的代码在全局执行上下文中。它做了两件事:1.创建一个全局对象,也就是浏览器中的window对象。2.将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都有自己的执行上下文,但它仅在函数被调用时创建。程序中可以存在任意数量的函数执行上下文。每当创建新的执行上下文时,它都会按特定顺序执行一系列步骤,这将在本文后面讨论。eval函数执行上下文:在eval函数中运行的代码也会得到自己的执行上下文,但是由于eval函数不是Javascript开发者常用的,这里就不展开讨论了。ExecutionStack执行栈,在其他编程语言中也称为调用栈,具有LIFO(后进先出)结构,用于存储代码执行过程中创建的所有执行上下文。当JavaScript引擎第一次读取您的脚本时,它会创建一个全局执行上下文并将其推送到当前执行堆栈。每当发生函数调用时,引擎都会为该函数创建一个新的执行上下文并将其推送到当前执行堆栈的顶部。引擎将运行其执行上下文位于执行堆栈顶部的函数。当函数运行结束后,其对应的执行上下文会从执行栈中弹出,上下文控制会被移动到当前执行栈的下一个执行上下文中。让我们通过以下代码示例来理解这一点:leta='HelloWorld!';functionfirst(){console.log('Insidefirstfunction');第二();console.log('再次进入第一个函数');}functionsecond(){console.log('Insidesecondfunction');}第一的();console.log('内部全局执行上下文');当上述代码在浏览器中加载时,JavaScript引擎会创建一个全局执行上下文并将其压入当前执行栈。当first()函数被调用时,JavaScript引擎会为该函数创建一个新的执行上下文并将其推送到当前执行堆栈的顶部。当在first()函数中调用second()函数时,Javascript引擎会为该函数创建一个新的执行上下文并将其压入当前执行堆栈的顶部。当second()函数执行时,它的执行上下文从当前执行栈中弹出,上下文控制会被移动到当前执行栈的下一个执行上下文,也就是first()函数的执行上下文。当first()函数被执行时,它的执行上下文从当前执行栈中弹出,上下文控制将被移动到全局执行上下文中。一旦执行完所有代码,Javascript引擎就会从执行堆栈中删除全局执行上下文。执行上下文是如何创建的到目前为止,我们已经了解了JavaScript引擎是如何管理执行上下文的,现在让我们了解一下JavaScript引擎是如何创建执行上下文的。执行上下文的创建分为两个阶段:1)创建阶段;2)执行阶段创建阶段在执行任何JavaScript代码之前,执行上下文处于创建阶段。在创建阶段一共发生了三件事:确定this的值,也称为ThisBinding。LexicalEnvironment组件已创建。VariableEnvironment组件已创建。因此,执行上下文可以在概念上表示如下:ofthis指向全局对象。在浏览器中,this的值指向window对象。在函数执行上下文中,this的值取决于函数的调用方式。如果使用对象引用调用它,则将this的值设置为该对象,否则将this的值设置为全局对象或未定义(在严格模式下)。例如:letperson={name:'peter',birthYear:1994,calcAge:function(){console.log(2018-this.birthYear);}}person.calcAge();//'this'指向'person',因为'calcAge'是用'person'对象引用调用的。让calculateAge=person.calcAge;计算年龄();//'this'指向全局窗口对象,因为没有给出对象引用词法环境(LexicalEnvironment)ES6官方文档对词法环境的定义是:LexicalEnvironment是一种规范类型,定义标识符与具体变量和函数之间的关联基于ECMAScript代码的词法嵌套结构。词法环境由环境记录和对外部词法环境的可能为空的引用(null)组成。简而言之,词法环境是一个包含标识符变量映射的结构。(这里的标识符代表变量/函数的名称,变量是对实际对象[包括函数类型对象]或原始值的引用。)在词法环境中,有两个组成部分:(1)环境record(环境记录)(2)对外部环境的引用环境记录是实际存放变量和函数声明的位置。对外部环境的引用意味着它可以访问其外部词法环境。有两种类型的词法环境:全局环境(在全局执行上下文中)是没有外部环境的词法环境。全局环境的外部环境引用为null。它有一个全局对象(窗口对象)及其关联的方法和属性(如数组方法)和任何用户定义的全局变量,this的值指向这个全局对象。在函数环境中,用户在函数中定义的变量存储在环境记录中。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。注意:对于函数环境,环境记录还包含一个参数对象,该对象包含传递给函数的索引和参数之间的映射以及传递给函数的参数的长度(数量)。例如,以下函数的参数对象如下所示:functionfoo(a,b){varc=a+b;}foo(2,3);//argumentsobjectArguments:{0:2,1:3,length:2},还有两种环境记录(如下图):Declarativeenvironmentrecordsstorevariables,functions,和参数。函数环境包含声明性环境记录。对象环境记录用于定义出现在全局执行上下文中的变量和函数的关联。全局环境包含对象环境记录。抽象地说,词法环境在伪代码中看起来像这样::"Declarative",//标识符绑定在这里如上所述,变量环境也是词法环境,因此它具有上面定义的词法环境的所有属性。在ES6中,LexicalEnvironment组件和VariableEnvironment组件的区别在于,前者用于存储函数声明和变量(let和const)绑定,而后者仅用于存储变量(var)绑定。让我们通过一些代码示例来理解上述概念:leta=20;常量b=30;varc;functionmultiply(e,f){varg=20;返回efg;}c=相乘(20,30);执行上下文看起来像这样:GlobalExectionContext={ThisBinding:,LexicalEnvironment:{EnvironmentRecord:{Type:"Object",//identifierbindingherea:,b:,multiply:}outer:},VariableEnvironment:{EnvironmentRecord:{Type:"Object",//标识符绑定在这里c:undefined,}outer:}}FunctionExectionContext={ThisBinding:,LexicalEnvironment:{EnvironmentRecord:{Type:"Declarative",//标识符绑定在这里Arguments:{0:20,1:30,length:2},},outer:},VariableEnvironment:{EnvironmentRecord:{Type:"Declarative",//这里绑定了标识符g:undefined},outer:}}注意:函数执行上下文只会在调用函数multiply时创建。您可能已经注意到,由let和const定义的变量没有任何与之关联的值,但是由var定义的变量被设置为undefined。这是因为在创建阶段,代码被扫描并解析变量和函数声明,其中函数声明存储在环境中,变量被设置为未定义(在var的情况下)或未初始化(在let和const大小写)。这就是为什么你可以在声明前访问var(虽然是undefined)定义的变量,但是如果在声明前访问let和const定义的变量,就会提示引用错误。这就是我们所说的变量提升。执行阶段这是整篇文章中最简单的部分。在这个阶段,所有变量的赋值都完成了,最后执行代码。注意:在执行阶段,如果Javascript引擎在源代码中实际声明的地方找不到let变量的值,那么它会被赋值为undefined。总结我们已经讨论了JavaScript是如何在内部执行的。虽然你没有必要学习所有这些概念才能成为一名优秀的JavaScript开发人员,但理解上述概念将帮助你更轻松、更深入地理解其他概念,如提升、作用域和闭包。