定义
是一种规范策略,用来通过ECMAScript编译器追踪代码运行时
简单来说就是代码执行时所在环境的抽象模型,是代码内标识符的集合抽象。
可执行代码(Executable Code)
ECMAScript可执行代码有四种类型
- global code:整个js文件。
- function code:函数代码。
- module:模块代码
- eval code:放在eval的代码。
组成部分
执行上下文(Execution Context)由三个组件组成
- LexicalEnvironment:词法环境组件
- VariableEnvironment:变量环境组件
- VariableEnvironment和LexicalEnvironment始终引用词法环境对象
- 初始化时VariableEnvironment和LexicalEnvironment引用同一个词法环境对象
- VariableEnvironment的引用不可变,LexicalEnvironment的引用会随着执行发生改变(块级)
- ThisBinding:this绑定
执行上下文栈(Execution Context Stack)
是一个先进后出的栈式结构(LIFO),用来跟踪维护执行上下文。运行执行上下文(running execution context) 始终位于执行上下文栈的顶层
每当从当前执行代码运行至其他可执行代码时,就会创建新的执行上下文,并将其压入执行栈并成为新的运行执行上下文。当相关代码执行完毕返回后,将正在运行的执行上下文从栈中弹出销毁,之前的执行上下文又成了运行执行上下文。也叫作调用栈。
执行上下文栈工作过程动图演示
- 开始执行任何JS代码前,会先创建全局上下文并压入栈,所以全局上下文一直在栈底
- 每次调用函数都会创建新的上下文(在函数内调用自身也是),并压入栈
- 函数执行完毕后返回,其执行上下文出栈销毁
- 所有代码执行完毕,执行上下文栈只剩全局执行上下文
代码执行
在任意的JavaScript可执行代码被执行时,执行步骤可按如下理解:
- 创建一个新的执行上下文(Execution Context)
- 创建一个新的词法环境(Lexical Environment)
- 将VariableEnvironment和LexicalEnvironment 都指向新创建的词法环境
- 将该执行上下文 推入执行栈 并成为 正在运行的执行上下文
- 对代码块内的 标识符进行实例化及初始化
- 运行代码
- 运行完毕后执行上下文出栈销毁
变量提升(Hoisting)与暂时性死区(temporal dead zone,TD)
变量提升发生在上述步骤的第五步,对代码块内的标识符进行实例化及初始化的具体表现如下:
- 执行代码块内的
let、const和class等声明的标识符合集记录为lexNames - 执行代码块内的
var和function声明的标识符集合记录为varNames - 如果
lexNames内的任何标识符在varNames或lexNames中重复出现,就会报错SyntaxError- 这就是为什么var和function可以重复声明,而let、const、class等不行
- 将
varNames中var声明的标识符实例化并初始化赋值undefined,同名的只操作最后一个 - 将
lexNames中的标识符实例化,但不会进行初始化(即依旧会提升,但无法访问)- 在运行至其声明处代码时才会进行初始化,在初始化之前访问都会报错
- 这就是暂时性死区,let、const和class声明的变量其实提升了,只是没有被初始化,初始化之前不可访问
- 最后将 varNames 内的函数声明实例化,并初始化赋值对应的函数体
为什么会有两个环境组件
首先声明组件的作用
- VariableEnvironment:记录 var 和 function 声明的标识符
- LexicalEnvironment:记录其他声明的绑定,如let、const、class等
一般情况下 Exexution Contexts 中的 VariableEnvironment 和 LexicalEnvironment 指向同一个词法环境,之所以区分两个组件,是为了实现块级作用域的同时不影响var及函数声明
块级作用域实现方式
- 一个正在执行的上下文中,
VariableEnvironment和LexicalEnvironment指向同一个词法环境,记录了所有的变量声明 - 当执行到块级代码时,会将
LexicalEnvironment记录下来,记录为oldEnv - 然后创建一个新的
LexicalEnvironment和词法环境(外部引用outer指向oldEnv),记录为newEnv - 将
newEnv设置为正在执行上下文的LexicalEnvironment - 块级代码内的
let、const等会绑定在newEnv上 - 而
var和function还是绑定在原本的VariableEnvironment上面- 块级代码内的函数会被当做var声明,会被提升至外部环境,块级代码运行前其值为初始值 undefined
- 块级代码执行完毕后,又将
oldEnv还原正在执行上下文的lexicalEnvironment
目前包括块级代码(在一对大括号内的代码)、for、switch、TryCatch语句中的catch从句及with语句(with语句创建的新环境为对象式环境,其他皆为声明式环境)都是这样来实现块级作用域的
