前言
本文字数2.5k字,请读者耐心看完,会有收获,先赞后看,养成习惯
执行上下文在JS进阶中非常重要的,其内部的原理涉及到JS很多特性,而执行上下文中有很多难以理解的概念,比较抽象,令我们难以下咽,所以本文将以通俗易懂的方式来带大家构建执行上下文的知识体系,下面将分别从概念,分类,创建,执行,销毁,问题
概念📚
我根据官方文档以及内部原理对于执行上下文的概念理解大概为:在我们的JS代码开始执行的时候抽象出来的一个隔离环境,该环境通过一些方式存储并控制变量,函数的访问权限。
执行上下文为什么那么重要,因为它的原理牵扯到了很多问题,比如为什么var
会发生变量提升现象而let
与const
不会,又比如作用域是如何控制变量访问的,文章最后会一一为大家解答。
分类😶🌫️
上下文分为三种,分别为:全局上下文,函数上下文,Eval()函数上下文,下面将主要讨论全局上下文与函数上下文
- 全局上下文:全局上下文在最外层,也就会涉及到我们熟知的顶层对象(
window
,global
) - 函数上下文:每个函数都有自己的执行上下文,所以称为函数上下文,函数的执行会触发函数上下文的一些行为
上下文创建🔛
上下文创建我们所需要知道的是创建时机及其生命周期,生命周期包括
this
绑定,词法环境创建,变量环境创建
以伪代码形式来表示上下文的抽象结构,并进一步理解上下文创建发生的三个过程
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 复制代码
一个上下文初始结构如上,ThisBinding
为this
绑定指向,LexicalEnvironment
为词法环境对象,VariableEnvironment
为变量环境对象,下文将具体介绍每个部分的伪代码的具体组成,以助于更好的理解
创建时机
不同类别上下文的创建时机也不同
- 全局上下文:当脚本被执行,就会立刻创建全局上下文
- 函数上下文:每当函数被执行,那么上下文会在执行前创建
this绑定
大家经常会用this
但是关于this
绑定是在哪个阶段进行的没有了解过,其实是在执行上下文创建的过程中将this
指向它应该指向的对象,其中全局上下文和函数上下文创建过程中的this
绑定不同
- 全局上下文:其
this
会指向全局对象(window,global等等) - 函数上下文:其
this
会指向函数执行时的调用者(对象,全局或者其他)
其实大家对于this
的机制很熟悉,所以这部分也可以很直接地理解,那么疑问就来了,箭头函数的创建上下文是什么类别的呢?
- 首先箭头函数的上下文是函数上下文
- 其次上下文是比较特殊的,它的上下文就是父级(函数or全局)的上下文
箭头函数上下文的特性导致了其箭头函数内部的this
是其定义所在的对象,另外也能想明白为什么箭头函数无法使用arguments
参数对象,关于箭头函数无法使用参数对象这个问题我们看完下面的词法环境创建后就会理解。
词法环境创建
关于词法环境(LexicalEnvironment
对象),掘友只需要记住其作用是解析当前上下文中由const
与let
声明的变量,将标识符与对应变量或者是函数关联起来即可,上下文的创建于执行都会涉及到词法环境的变化,让我们带着作用往下学习!
词法环境是由一个环境记录器与一个外部环境引用构成
- 环境记录器:存储变量和函数的实际位置
- 外部环境引用:在词法环境中以属性值存储外部环境的引用地址,那么就可以访问父级环境的变量等一系列东西
从上面两个组成角度看创建阶段发生了什么,下面这是一个基本词法环境的伪代码(空)
LexicalEnvironment: { EnvironmentRecord: { } outer: <null> }, 复制代码
LexicalEnvironment
代表词法环境,EnvironmentRecord
代表环境记录器,outer
代表外部环境引用,我们使用let
或者 const
声明的变量,会在创建词法环境的过程中解析,解析后并将其标识符存放在环境记录器中,以未初始化的形式保存,比如我声明了一个a
变量,这个时候会在EnvironmentRecord
存放标识符a
,其值为< uninitialized >
即未初始化。
LexicalEnvironment: { EnvironmentRecord: { Type: "Object", a: < uninitialized >, } outer: <null> }, 复制代码
上面就是一个词法环境创建过程中发生了什么,我们回过头想起词法环境的作用:解析当前上下文中由const
与let
声明的变量,将标识符与对应变量或者是函数关联起来,那么创建阶段就是在解析声明后,存放标识符,而当前由于是let
与const
声明,并没有进行初始化,所以其值为< uninitialized >
。
另外词法环境分为两种,其应用场景不相同
- 函数环境:函数执行上下文创建函数词法环境,其环境记录器为对象环境记录器,,除此之外其外部环境引用指向其父级上下文(可能是函数也可能是全局)。
- 全局环境:全局执行上下文会创建全局词法环境,其环境记录器为声明式环境记录器,不包含
arguments
参数对象,由于没用父级,所以其外部环境引用为null