执行上下文(Exexution Contexts):用来经过ECMAScript编译器来追踪代码运行时计算的一种规范策略。git
执行上下文简单理解就是代码执行时所在环境的抽象。github
执行上下文同时包含变量环境组件(VariableEnvironment)和词法环境组件(LexicalEnvironment),这两个组件多数状况下都指向相同的词法环境(Lexical Environment),那为何还要存在两个环境组件呢?咱们稍后将进行详细讨论。若是不太了解词法环境的能够看下个人上一篇文章深刻ECMAScript系列(一):词法环境。函数
ExecutionContext = {
VariableEnvironment: { ... },
LexicalEnvironment: { ... },
}
复制代码
执行上下文栈(Execution Context Stack):是一个后进先出的栈式结构(LIFO),用来跟踪维护执行上下文。运行执行上下文(running execution context) 始终位于执行上下文栈的顶层。那么何时会建立新的执行上下文呢?post
ECMAScript可执行代码有四种类型:全局代码,函数代码,模块代码和eval
。每当从当前执行代码运行至其余可执行代码时,会建立新的执行上下文,将其压入执行上下文栈并成为正在运行的执行上下文。当相关代码执行完毕返回后,将正在运行的执行上下文从执行上下文栈删除,以前的执行上下文又成为了正在运行的执行上下文。ui
咱们经过一个动图来看一下执行上下文栈的工做过程spa
上面提到过ECMAScript可执行代码有四种类型:全局代码,函数代码,模块代码和eval
。code
这里虽说是全局代码,可是JavaScript引擎实际上是按照
script
标签来解析执行的,也就是说script
标签按照它们出现的顺序解析执行,这也就是为何咱们平时要将项目依赖js库放在前面引入的缘由。cdn
JavaScript引擎是按可执行代码块来执行代码的,在任意的JavaScript可执行代码被执行时,执行步骤可按以下理解:对象
咱们日常所说的变量提高就发生在上述执行步骤的第四步,对代码块内的标识符进行实例化及初始化的具体表现以下:ip
let
、const
和class
声明的标识符合集记录为lexNames
var
和function
声明的标识符合集记录为varNames
lexNames
内的任何标识符在varNames
或lexNames
内出现过,则报错SyntaxError
这就是为何能够用
var
或function
声明多个同名变量,可是不能用let
、const
和class
声明多个同名变量。
varNames
内的var
声明的标识符实例化并初始化赋值undefined
,若是有同名标识符则跳过
这就是所谓的变量提高,咱们用
var
声明的变量,在声明位置以前访问并不会报错,而是返回undefined
lexNames
内的标识符实例化,但并不会进行初始化,在运行至其声明处代码时才会进行初始化,在初始化前访问都会报错。
这就是咱们所说的暂时性死区,
let
、const
和class
声明的变量其实也提高了,只不过没有被初始化,初始化以前不可访问。
varNames
内的函数声明实例化并初始化赋值对应的函数体,若是有同名函数声明,则前面的都会忽略,只有最后一个声明的函数会被初始化赋值。
函数声明会被直接赋值,全部咱们在函数声明位置以前也能够调用函数。
首先明确这两个环境组件的做用,变量环境组件(VariableEnvironment)
用于记录var
声明的绑定,词法环境组件(LexicalEnvironment)
用于记录其余声明的绑定(如let
、const
、class
等)。
通常状况下一个Exexution Contexts
内的VariableEnvironment
和LexicalEnvironment
指向同一个词法环境,之因此要区分两个组件,主要是为了实现块级做用域的同时不影响var
声明及函数声明。
众所周知,ES6以前并无块级做用域的概念,可是ES6及以后咱们能够经过新增的let
及const
等命令来实现块级做用域,而且不影响var
声明的变量和函数声明,那么这是怎么实现的呢?
running Execution Context
)内,词法环境由VariableEnvironment
和LexicalEnvironment
构成,此执行上下文内的全部标识符的绑定都记录在两个组件的环境记录内。LexicalEnvironment
记录下来,咱们将其记录为oldEnv
。LexicalEnvironment
(外部词法环境outer
指向oldEnv
),咱们将其记录为newEnv
,并将newEnv
设置为running Execution Context
的LexicalEnvironment
。let
、const
等声明就会绑定在这个newEnv
上面,可是var
声明和函数声明仍是绑定在原来的VariableEnvironment
上面。
块级代码内的函数声明会被当作
var
声明,会被提高至外部环境,块级代码运行前其值为初始值undefined
console.log(foo) // 输出:undefined
{
function foo() {console.log('hello')}
}
console.log(foo) // 输出: ƒ foo() {console.log('hello')}
复制代码
oldEnv
还原为running Execution Context
的LexicalEnvironment
。目前包括块级代码(在一对大括号内的代码)、for
循环语句、switch
语句、TryCatch
语句中的catch
从句以及with
语句(with
语句建立的新环境为对象式环境,其余皆为声明式环境)都是这样来实现块级做用域的。
准备将以前写的部分深刻ECMAScript文章重写,加深本身理解,使内容更有干货,目录结构也更合理。
欢迎前往阅读系列文章,若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。
菜鸟一枚,若是有疑问或者发现错误,能够在相应的 issues 进行提问或勘误,与你们共同进步。