这是一篇笔记,记录在学习执行上下文时补充的一些知识点,方便日后回忆
JavaScript 中有三种执行上下文类型。
- 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置
this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。 - 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
- Eval 函数执行上下文 — 执行在
eval函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用eval,所以在这里我不会讨论它。
ES3 中的执行上下文:
- 类型:
- 全局执行上下文:整个程序中只有一个,在脚本生命周期内存在于执行堆栈底部。会生成全局对象(如浏览器中的
window),并将this绑定到全局对象。 - 函数执行上下文:每当函数被调用时创建。
Eval函数执行上下文:由于不常用,暂不分析。
- 全局执行上下文:整个程序中只有一个,在脚本生命周期内存在于执行堆栈底部。会生成全局对象(如浏览器中的
- 内容:
- 变量对象(
VO):每个执行环境都有,全局执行环境的变量对象始终存在,函数执行环境的变量对象在函数调用且执行代码前,用参数列表初始化,函数声明变量声明会作为属性添加。只有函数声明会被加入,函数表达式会被忽略。 - 活动对象(
AO):函数进入执行阶段,变量对象被激活成为活动对象,此时可访问其中的属性。 - 作用域链:规定变量方式,从当前上下文变量对象开始,若未找到则从父级查找,直到全局对象。函数创建时确定作用域,执行时构建作用域链。
- 当前可执行代码块的调用者(this`):根据调用方式确定,如对象方法调用、使用特定 API 委托调用等,否则默认为全局对象调用。
- 变量对象(
- 生命周期:
- 创建阶段:函数调用时且执行函数体内前,用参数初始化变量对象,进行变量和函数的初始化声明(变量声明提升),构建作用域链,确定
this值。 - 执行阶段:逐条执行代码,对变量赋值,访问变量,函数调用创建新执行上下文等。
- 销毁阶段:一般函数执行完,局部执行上下文弹出销毁,闭包情况特殊,会导致函数变量对象驻存内存,直到闭包引用销毁。
- 创建阶段:函数调用时且执行函数体内前,用参数初始化变量对象,进行变量和函数的初始化声明(变量声明提升),构建作用域链,确定
ES5 中的执行上下文:
-
执行上下文的组成:
ThisBinding:表示this的值。LexicalEnvironment:词法环境组件,类似于 ES3 中的变量对象,用于存储变量和函数的映射关系。VariableEnvironment:变量环境组件,为 ES6 服务,处理var变量绑定。
-
执行过程:
全局上下文被创建
-
-
创建全局上下文的 词法环境
-
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
let和const定义的变量)
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
-
- 创建 外部环境引用,值为
null
- 创建 外部环境引用,值为
-
-
-
创建全局上下文的 变量环境
-
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
var定义的变量,初始值为undefined造成声明提升)
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
-
- 创建 外部环境引用,值为
null
- 创建 外部环境引用,值为
-
-
- 确定
this值为全局对象(以浏览器为例,就是window)
- 确定
函数被调用,函数上下文被创建
-
-
创建函数上下文的 词法环境
-
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理let和const定义的变量)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
- 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
-
-
-
创建函数上下文的 变量环境
-
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理var定义的变量,初始值为undefined造成声明提升)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
- 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
-
-
- 确定
this值
- 确定
在上下文创建阶段,引擎会检查代码找出变量和函数声明,并进行相应的初始化设置。对于 var 变量,初始值为 undefined;对于 let 和 const 变量,在声明前访问会得到引用错误。
总的来说,ES5 执行上下文的变更主要是概念上的调整,本质与 ES3 差别不大,更多是为 ES6 中的新特性做铺垫。
对比ES3和ES5、ES6在执行上下文方面存在的差异:
变量对象和活动对象:
- ES3 中有变量对象(VO)和活动对象(AO)的概念,函数执行时变量对象会激活为活动对象。
- ES5 去除了变量对象和活动对象,以词法环境组件(LexicalEnvironment component)和变量环境组件(VariableEnvironment component)替代。
词法环境和变量环境:
- ES5 中的词法环境是一种规范类型,基于代码的词法嵌套结构定义标识符和变量、函数的关联。
- ES5 中的变量环境也是词法环境,为 ES6 服务,在 ES6 中词法环境组件和变量环境在存储变量绑定上有不同。
- ES6引入了块级作用域,每个块级作用域都会创建新的词法环境 ES5只有全局作用域和函数作用域
变量初始化:
- ES3 中函数执行上下文阶段,变量统一定义为
undefined,函数直接定义。 - ES5 中在上下文创建阶段,引擎检查代码找出变量和函数声明,变量最初会设置为
undefined(var情况下),或者未初始化(let和const情况下)。
this绑定:
- ES5中普通函数的
this由调用方式决定 - ES6的箭头函数没有自己的
this,继承自外层作用域的this
执行上下文的创建过程:
- ES3 中函数执行上下文创建阶段主要包括初始化变量对象、构建作用域链、确定
this值。 - ES5 中全局上下文和函数上下文创建过程更加复杂和细致,包括创建词法环境变量环境的对象环境记录器、外部环境引用等,并确定
this值。
变量对象的创建过程
(我理解环境记录器应该是相同逻辑):
-
函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
-
函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性
-
变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建;
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
参考链接: