这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
执行上下文是 浏览器在执行JS中的一个环节之一,让我们来分析一下其中的流程。
一、概括
执行上下文,就是只当前正在执行的环境,分为两个阶段:
- 创建阶段
- 执行阶段
二、内容
首先我们对JS中的环境进行分类:
- 全局环境(js代码加载完毕后,进入到预编译也就是进入到全局环境)
- 函数环境(函数调用的时候,进入到该函数环境,不同的函数,函数环境不同)
eval环境(不建议使用,存在安全、性能问题)
(一) 创建过程
创建执行上下文过程分为三部分:
- 确定
this的指向(This Binding) - 词法环境(LexicalEnvironment)组件创建
- 变量环境(VariableEnvironment)组件创建
1. this指向(This Binding)
- 全局环境:
this指向全局对象,如:浏览器中的this指向windows对象,而在nodeJs中指向module对象
- 局部(函数)环境:
this取决于调用函数时的方法,具体由:默认绑定、隐式绑定、显式绑定(硬绑定)、new绑定、箭头函数。例如:bind,apply, 箭头函数 这些改变this指向的方法等。
2. 词法环境(LexicalEnvironment)组件创建
词法环境由环境记录与对外部环境引入记录两个部分组成。
- 环境记录:存储当前环境中的变量和函数声明的位置(只是知道当前环境里有什么变量和函数,还没有进行赋值操作),包含了
arguments对象。- 全局环境记录:记录当前环境下所有的属性,方法的位置。
- 函数环境记录:包含了函数内定义的所有的属性和方法外,还包含了一个
arguments对象。
- 外部环境引入记录:存储可以通过自身环境访问到的其他的环境,有点作用域链的感觉。
- 全局外部环境引入:外部环境引入为
null,因为本身就是最外层环境。 - 函数外部环境引入:可以是 全局环境、也可以是其他函数环境。
- 全局外部环境引入:外部环境引入为
GlobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境
// 标识符绑定在这里
outer: <null> // 对外部环境的引用
}
}
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative", // 函数环境
// 标识符绑定在这里 // 对外部环境的引用
outer: <Global or outer function environment reference>
}
}
3. 变量环境(VariableEnvironment)组件创建
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。
在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量( let 和 const ) 绑定,而后者仅用于存储变量( var ) 绑定。
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
变量提升:
在创建阶段,函数声明存储在环境中,而变量会被设置为undefined(在var的情况下)或保持未初始化(在let和const的情况下)。所以这就是为什么可以在声明之前访问var定义的变量(尽管是undefined),但如果在声明之前访问let和const定义的变量就会提示引用错误的原因。这就是所谓的变量提升。
(二) 执行过程
每个执行上下文里,都有三个重要的属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
this变量对象(Variable object,VO)
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
不同执行上下文下的变量对象稍有不同。
- 全局上下文的变量对象是全局对象
- 函数上下文中,我们用 活动对象(activation object, AO) 表示变量对象,两者是一个东西,但也有区别:
- 变量对象(VO) 是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
- 当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。
活动对象是在 进入函数上下文时被创建的,它通过函数的
arguments属性初始化。arguments属性值是Arguments对象。
执行过程可以分为两部分:
- 进入执行上下文(分析)
- 代码执行(执行)
1. 进入执行上下文
此时还未执行代码,而当前环境的变量对象(或活动对象)包括了:
- 函数的所有形参(若当前环境为函数环境的话):由名称和对应值组成一个变量对象的属性与值,如果没有传入实参,则值为
undefined - 函数声明:环境内的所有函数,由名称和对应值(函数对象(
function-object)) 组成一个变量对象的属性与值,若变量对象中已存在相同名称的属性则会进行替换。 - 变量声明:环境内的所有变量,由名称和对应值(undefined或未定义)组成一个变量对象的属性与值,若变量名称与声明的形参或函数相同,则不会干扰已存在的这类属性。
2. 代码执行
在这个阶段,会从上而下按顺序执行代码,并根据代码,修改变量对象的各个属性的值。
举例子:
一个函数环境
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
进入执行上下文后,此时的 AO(活动对象) 是
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
到了代码执行,此时的 AO(活动对象) 是
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
三、总结
执行上下文的总流程是这样的:
- 创建阶段:
- 确定 this 的指向 (This Binding)
- 词法环境(LexicalEnvironment)组件被创建
- 变量环境(VariableEnvironment)组件被创建
总的来说就是初始化变量对象(Variable Object)
确定
this的指向
初始化内置对象(函数环境为arguments,全局环境为Date、Math等内置对象)
查找当前环境所有形参(函数环境)
查找当前环境所有的函数声明
查找当前环境所有的变量声明
查找当前环境所有的外部引用(作用域链)
- 执行阶段
- 对变量对象中的属性进行赋值
- 对变量对象中的方法进行函数引用
- 执行代码
即获取环境中所有变量和函数的值后赋值到变量对象(或活动对象)里,然后执行代码
有什么问题希望大家可以在评论区指出,我及时纠正。
新人上路,还请多多包含。
我是MoonLight,一个初出茅庐的小前端。