Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
本题难度:⭐ ⭐ ⭐
答:
执行上下文是 JavaScript 执行一段代码时的运行环境,包含了这段代码执行期间用到的诸如 this、变量、对象以及函数等环境信息。
只要有 Javascript 代码运行,那么它就一定是运行在执行上下文中。
执行上下文的类型分为三种:
- 全局执行上下文:只有一个,浏览器中的全局对象就是
window对象,this指向这个全局对象 - 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
- Eval 函数执行上下文: 指的是运行在
eval函数中的代码,很少用而且不建议使用,本文不讨论它。
分析
环境是什么?人为什么生活在地球上而不是月球上,因为地球有人赖以生存的空气、水、食物等等东西,而月球没有。
代码运行的环境是什么?JS 代码要能正常执行,就得需要用到变量、作用域、this值等一系列信息,这些信息就是设计 JS 这门语言的人定义的规则,而一段代码执行所需的所有信息就被定义为执行上下文。
一段 JS 代码是以怎样的顺序解析和执行的?代码中的那些变量是何时被定义的?变量之间错综复杂的访问关系又是怎样创建和连接的?要解释这些问题,就必须了解 JS 执行上下文的概念。
执行上下文创建流程
当一段代码被执行时,JavaScript 引擎先会对其进行编译,并创建执行上下文。
首先,机器是无法直接识别高级语言代码的,要先转换成计算机能读懂的机器语言,
而 JS 是一门解释型语言,转换代码的过程差不多如下图:
将源代码转换为抽象语法树的过程中,会生成执行上下文,也就是执行上下文的创建阶段,会在这个阶段确定 this值,创建 词法环境 和 变量环境。
比如,这样一段代码,
var x = 2
function sum(){
var y = 10
return x + y
}
sum()
在执行到函数 sum() 之前,JavaScript 引擎会为上面这段代码创建全局执行上下文,包含了声明的函数和变量,如下图:
从图中可以看出,代码中全局变量保存在全局上下文的变量环境中,函数保存在词法环境中。
执行上下文准备好之后,便开始执行全局代码,当执行到 sum 这儿时,JavaScript 判断这是一个函数调用,那么将执行以下操作:
- 首先,从全局执行上下文中,取出 sum 函数代码。
- 其次,对 sum 函数的这段代码进行编译,并创建该函数的执行上下文和可执行代码。
- 最后,执行代码,输出结果。
完整流程如下图:
拓展:结合伪代码分析创建阶段
执行上下文经历了很多版本的迭代,本文分析的是ES5版本中的执行上下文。
执行上下文创建阶段做了三件事:
- 确定 this 的值,也被称为
This Binding - 词法环境(LexicalEnvironment)被创建
- 变量环境(VariableEnvironment)被创建
// 伪代码
ExecutionContext = {
ThisBinding = <this value>, // 确定this
LexicalEnvironment = { ... }, // 词法环境
VariableEnvironment = { ... }, // 变量环境
}
确定 this
this的值是在执行的时候才能确认,定义的时候不能确认,
全局上下文的 this 指向 window。
函数上下文的 this 指向不是固定不变的,取决于函数处于什么位置、以什么方式调用,可以总结成如下图:
关于this,更多可参考我的这篇文章: 「前端每日一问(24)」说一下 JS 中的 this
词法环境
词法环境(lexical environment)是 JavaScript 引擎内部用来跟踪标识符与特定变量之间的映射关系。比如,看下面的代码:
var name = 'lin'
console.log(name)
当 console.log 语句访问 name 变量时,会进行词法环境的查询。
词法环境是 JS 作用域的内部实现机制,人们通常称为作用域(scope)。
词法环境有两个组成部分:
- 全局环境:是一个没有外部环境的词法环境,其外部环境引用为
null,有一个全局对象,this的值指向这个全局对象 - 函数环境:用户在函数中定义的变量被存储在环境记录中,包含了
arguments对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
伪代码如下:
// 伪代码
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 在这里绑定标识符
}
outer: <null>
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 在这里绑定标识符
}
outer: <Global or outer function environment reference>
}
}
变量环境
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性
在 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)
执行上下文如下:
// 伪代码
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { // 词法环境
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: { // 变量环境
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
注意,let和const定义的变量a和b在创建阶段没有被赋值,但var声明的变量从在创建阶段被赋值为undefined。
这是因为,创建阶段,会在代码中扫描变量和函数声明,然后将函数声明存储在环境中。
但变量会被初始化为undefined(var声明的情况下)和保持uninitialized(未初始化状态)(使用let和const声明的情况下)。
这就是变量提升的实际原因。
参考文章:
结尾
本文完全是八股文,看不懂没关系,不影响继续做 API 工程师。
但看懂了还是有点帮助,给我的感受是,查阅了很多资料,把这些八股文的理念勉强搞懂之后,很多知识点都被串联起来了。
如果我的文章对你有帮助,你的👍就是对我的最大支持^_^
你也可以关注《前端每日一问》这个专栏,防止失联哦~
我是阿林,输出洞见技术,再会!
上一篇:
下一篇: