什么是执行上下文?
执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文。
什么是执行栈?
执行栈,在其他编程语言中也被叫做调用栈,具有LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。当JavaScript引擎首次读取脚本时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当发生一个函数调用,引擎都会为该函数创建一个新的执行上下文,并将其推到当前执行栈的顶端。引擎会运行执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文。所以程序结束以前,执行栈最底部永远有一个globalContext。
执行上下文的类型
执行上下文总共有三种类型:
一、全局执行上下文:这是默认的,基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中,做了两件事:1.创建一个全局对象,在浏览器中这个全局对象就是window对象。2.将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
二、函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤。
三、Eval函数执行上下文:运行在eval函数中的代码也获得自己的执行上下文,不常用函数,不建议使用。
执行上下文如何被创建?
执行上下文分两个阶段创建:1)创建阶段;2)执行阶段
一、创建阶段:在任意的JavaScript代码被执行之前,执行上下文处于创建阶段。在创建阶段中总共发生了三件事情:
1.确定this的值,也被称为This Binding;
2.LexicalEnvironment(词法环境)组件被创建。
3.VariableEnvironment(变量环境)组件被创建。
创建阶段
Ⅰ、This Binding:
在全局执行上下文中,this的值指向全局对象,在浏览器中,this的值指向window对象。在函数指向上下文中,this的值取决于函数的调用方式。如果它被一个对象引用调用,那么this的值被设置为该对象,否则this的值被设置为全局对象或undefined(严格模式下)。
Ⅱ、LexicalEnvironment(词法环境):
ES6文档将词法环境定义为:词法环境是一种规范类型,基于ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系(environment record)和可能为空引用(null)的外部词法环境组成。简而言之,词法环境是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包含函数类型对象】或原始值的引用)
在LexicalEnvironment(词法环境)中,有两个部分组成:(1)环境记录(environment record):是存储变量和函数声明的实际位置 (2)对外部环境的引用:意味着它可以访问外部词法环境。
LexicalEnvironment(词法环境)有两种类型:
1.全局环境(在全局执行上下文中)是一个没有外部环境的词法环境。全局环境的外部环境引用为null。它拥有一个全局对象(window对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this的值指向这个全局对象。
2.函数环境,用户在函数中定义的变量被存储在环境记录中,对外部环境的引用可以是全局,也可以是包含内部函数的外部函数环境。
注意:对于函数环境而言,环境记录还包含了一个arguments对象,该对象包含了索引和传递给函数的参数之间的映射以及传递给函数的长度(数量)。
环境记录同样有两种类型:
1.声明性环境记录:存储变量、函数和参数。一个函数环境包含声明性环境记录。
2.对象环境记录:用于定义在全局执行上下文中出现的变量和函数的关联。全局环境包含对象环境记录。
Ⅲ、VariableEnvironmen(变量环境):
它也是一个词法环境,其EnvironmentRecord包含了由VariableStatements在此执行上下文创建的绑定。它具有上面定义的语法环境的所有属性。与LexicalEnvironment的区别在于前者用于存储函数声明和变量(let和const)绑定,而后者仅用于存储变量(var)绑定。
结合实例分析:
let a=10;const b=20;var c;function addFun (e,f){var g =50;return e*f*g;}c = addFun(30,40)/*GlobalExecutionContext = {ThisBinding = <Global Object> //确定thisLexicalEnvironment = {EnvironmentRecord:{Type:'Object' //全局环境//标识符绑定a:<uninitialized>,b:<uninitialized>,addFun:<Func>},outer:<null> //对外部环境的引用}, //词法环境VariableEnvironment = {EnvironmentRecord:{Type:'Object' //全局环境//标识符绑定c:undefined},outer:<null> //对外部环境的引用} //变量环境}FunctionExecutionContext = {ThisBinding = <Global Object> //确定thisLexicalEnvironment = {EnvironmentRecord:{Type:'Declarative' //函数环境//标识符绑定Arguments:{0:30,1:40,length:2}},outer:<GlobalLexicalEnvironment>//对外部环境的引用}, //词法环境VariableEnvironment = {EnvironmentRecord:{Type:'Declarative' //函数环境//标识符绑定g:undefined},outer:<GlobalLexicalEnvironment>//对外部环境的引用} //变量环境}*/
只有在addFun调用的时候才会创建函数执行上下文。创建之初,代码会被扫描并解析变量和函数声明,其中函数声明存储在环境之中,而变量会被设置为undefined(在var情况下)或保持未初始化(在let和const情况下)。这也是为什么可以在声明之前访问var定义的变量(尽管是undefined),但如果在let或const之前访问定义的变量会提示引用错误的原因。
这就是所谓的变量提升。
函数上下文
在函数上下文中,用活动对象(activation object,AO)来表示变量对象。
活动对象和变量对象的区别在于:
1.变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
2.当进入到一个执行上下文后,这个变量才会被激活,所以叫活动变量(AO),这个时候活动变量上的各种属性才能被访问。
调用函数的时候,会为其创建一个Arguments对象,并自动初始化局部变量arguments,指代该Arguments对象。所有作为参数传入的值都会成为Arguments对象的数组元素。
二、执行阶段分为两个阶段。注:在执行阶段,如果JavaScript引擎在源代码中声明的位置找不到let变量的值,那么将为其分配undefined的值。
Ⅰ、进入执行阶段
此时的变量对象会进行初始化:
1.函数的所有形参:没有实参,属性值设为undefined。
2.函数声明:如果变量对象已经存在相同的属性,则完全替换这个属性。
3.变量声明:如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的属性。
Ⅱ、代码执行
这个阶段会顺序执行代码,修改变量对象的值。
执行阶段总结:
1.全局上下文的变量初始化是全局对象;
2.函数上下文的变量对象初始化只包括Arguments对象;
3.在进入执行上下文时会给变量对象添加形参,函数声明,变量声明等初始属性值
4.在代码执行阶段,会再次修改变量对象的属性值。