执行上下文
执行上下文
(Execution context)的概念在 JavaScript 中是颇为重要的。变量或函数的执行上下文
决定了它们可以访问哪些数据,以及它们的行为。抽象地讲,可以理解为一个运行环境盒子 ,JavaScript 代码在这个盒子里运行。
每个执行上下文都有一个关联的变量对象 (variable object),这个执行上下文中定义的所有变量和函数都存在于这个对象上,除此之外,还包含作用域链和 this 关键字的值。
JavaScript 中有两种类型的执行上下文:
- 全局执行上下文(Global Execution Context - GEC)
- 函数执行上下文(Function Execution Context - FEC)
上下文在其所有代码都执行完毕后被销毁,包括定义在它上面的所有变量和函数(GEC 在应用退出前才会被销毁,比如关闭网页或退出浏览器)。让我们结合如下代码详细了解一下两者。
js
var a = 10;
function greetings() {
let word = "hello";
function addExtra(str1, str2) {
return str1 + str2;
}
return addExtra("hello", "world");
}
greetings();
全局执行上下文
GEC 是基础的默认的执行上下文,执行所有不在函数内部的 JavaScript 代码。
根据 ECMAScript 实现的宿主环境,表示全局执行上下文的对象可能不一样。在浏览器中,GEC 就是我们常说的 window 对象,因此所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和方法,而使用 let 和 const 的顶级声明不会定义在 GEC 中。
对于每个JavaScript宿主环境,只能有一个全局上下文。
函数执行上下文
每当函数调用时,JavaScript 引擎都会在全局上下文中创建不同类型的执行上下文,称为函数执行上下文。 由于每个函数都有自己的函数执行上下文(一个函数不一定只有一个,因为)
执行上下文的创建
执行上下文(GEC 或 FEC)的创建分两个阶段进行:
- Creation Phase 创建阶段
- Execution Phase 执行阶段
Creation Phase 创建阶段
在创建阶段,执行上下文首先与执行上下文对象(Execution Context Object - ECO)相关联。创建阶段又分为3个阶段,在此期间定义和设置执行上下文对象的属性。这些阶段是:
- 创建变量对象(Variable Object - VO)
- 创建作用域链(Scope Chain)
- 设置
this
关键字的值
让我们详细回顾下每个阶段。
创建变量对象
前文提到,变量对象是在执行上下文中创建的类似对象的容器。它存储在该执行上下文中定义的变量和函数声明。 在 GEC 中,对于使用 var 关键字声明的每个变量,都会向 VO 中添加一个指向该变量并设置为 "undefined" 的属性。
此外,对于每个函数声明,都会向 VO 中添加一个指向该函数的属性,并且该属性存储在内存中。这意味着所有函数声明都将在 VO 中存储和访问,甚至在代码开始运行之前。
因为在代码执行之前就将变量和函数声明存储在内存中,由此引发了提升。
我们用伪代码来表示:
js
GEC = {
VO: {
a: undefined,
greetings: function greetings() { ... }
}
}
greetingsFEC = {
VO: {
word: nothing, // 注意这里不是 undefined
addExtra: function greetings() { ... }
}
}
创建作用域链
作用域链是当前作用域中可访问的变量对象列表。作用域中的每个变量对象都表示更高级别的作用域。
用伪代码表示:
js
GEC = {
VO: {
a: undefined,
greetings: function greetings() { ... }
},
scope: [ ...GEC.VO]
}
greetingsFEC = {
VO: {
word: nothing, // 注意这里不是 undefined
addExtra: function greetings() { ... }
},
scope: [ GEC.scope, ...greetings.VO]
}
设置 "this"
创建阶段里最后一步是设置 this 关键字的值。Javascript 中 this 关键字是指执行上下文所属的范围。创建作用域链后,JS 引擎将初始化 this 的值。
在 GEC 中,this 指的是全局对象,即 window 对象(浏览器环境)。
对于 FEC,它不会创建 this 对象,相反,它可以访问它所定义的环境。通俗地讲,是指向调用者,因此不会创建对象啦。
Execution Phase 执行阶段
这是实际代码执行开始的阶段。在此之前,VO 中包含值为 undefined 和 nothing 变量,如果运行代码则必然要更新为实际值。然后,代码由解析器解析,转换为可执行的字节码,最后执行。
执行上下文栈
执行上下文栈(Execution Context Stack)跟踪在脚本生命周期内创建的所有执行上下文。因为 JavaScript 是单线程语言,这意味着它一次只能执行一个任务。因此,当其他操作、函数和事件发生时,将为每个事件创建一个执行上下文,再将要其放入执行上下文栈中。如下图所示:
请注意这个栈中执行顺序,后进先出。