前言
作为和 JS 同步进化的底层基础概念,执行上下文的构成有很多版本,在我的上篇博客《深入理解JS(三) - 执行上下文、作用域链与闭包》中,为了方便理解作用域链和闭包,我采用了 ES3 的版本的执行上下文。
现在讲究与时俱进,我将在这篇博客中为大家补充理解 ES6 版本的执行上下文。
一. 定义
不管版本如何变化,执行上下文就是代码的执行环境,其中包含了这段代码执行所需的所有信息。
二. 构成
与 ES3 中的执行上下文构成(变量对象、作用域链、this
指针)不同,ES6 中的执行上下文由词法环境、变量环境、this
绑定三部分构成。其中词法环境、变量环境 = ES3的变量对象 + 作用域链。
-
生命周期: 执行上下文的生命周期按照 创建阶段 -> 执行阶段 -> 销毁阶段 的顺序依次进行。
-
在执行上下文的创建阶段,按照
this
绑定 -> 词法环境组件创建 -> 变量环境组件创建 的顺序进行创建。
词法环境(LexicalEnvironment)
因为 ES6 新增了块级作用域以及支持块级作用域的声明方式let
和const
,所以执行上下文也随之作出了改进,引入了词法环境,用于支持块级作用域。关于提升和TDZ的问题,本博客不做探讨,请移步《深入理解JS(一) - 提升与TDZ》。
- 其中存储
let
、const
、function
、class
、import
声明的变量。 - 示例:
-
let
、const
声明:function example() { // 词法环境(LexicalEnvironment) let a = 10; // let声明的变量 const b = 20; // const声明的常量 if (true) { let c = 30; // 块级作用域内的let声明 const d = 40; // 块级作用域内的const声明 } // 变量环境(VariableEnvironment) var e = 50; // var声明的变量(归变量环境) }
-
function
声明:function outer() { // 词法环境(LexicalEnvironment) function inner() { // function声明(属于词法环境) console.log('这是一个函数声明'); } // 变量环境(VariableEnvironment) var temp = "temporary"; // 函数声明会被提升到词法环境顶部,可以在声明前调用 inner(); // 正常执行:"这是一个函数声明" } outer();
-
class
声明:function testClass() { // 词法环境(LexicalEnvironment) class Person { // class声明 constructor(name) { this.name = name; } } const p = new Person("张三"); // 变量环境(VariableEnvironment) var temp = "temporary"; }
-
import
声明:// module.js export const PI = 3.14; export function sum(a, b) { return a + b; } // main.js(ES模块环境) import { PI, sum } from './module.js'; // import声明 function calculate() { // 词法环境(LexicalEnvironment) console.log(PI); // 导入的常量 const result = sum(1, 2); // 导入的函数 // 变量环境(VariableEnvironment) var message = "Result: " + result; }
-
变量环境(VariableEnvironment)
变量环境是词法环境的一种特殊类型,JS 引入let
和const
后,因为var
声明不支持块级作用域,专门分离出来用于存储var
声明。
- 其中只存储
var
声明的变量。 - 示例:
-
var
声明的变量:function compareEnvironments() { var x = 1; // 存储在变量环境中 let y = 2; // 存储在词法环境中 if (true) { var x = 10; // 覆盖函数级变量环境中的 x (var声明不支持块级作用域) let y = 20; // 创建块级词法环境中的新变量 y console.log(x, y); // 输出:10 20 } console.log(x, y); // 输出:10 2(词法环境中的 y 未受影响) } compareEnvironments();
-
this 绑定(This Binding)
这里的 this
绑定大致与 ES3 版本的 this
指针指向规则一致,即为由调用方式决定,但是有个别不同。
- 普通函数与箭头函数: ES6 出现了箭头函数,使得
this
的指向出现了变化。- 普通函数:
this
的值取决于函数的调用方式(全局、函数、构造函数、方法调用等),动态绑定。 - 箭头函数:
this
继承自外层执行上下文,不随调用方式改变,静态绑定。
- 普通函数:
- 全局作用域与模块作用域: 在 ES6 新引入的模块作用域情况下,
this
的指向也有不同。- 全局作用域: 全局作用域中
this
指向全局对象(如浏览器中的window
以及Node.js中的global
)。 - 模块作用域: 全局作用域中
this
仍指向全局对象,但模块作用域中this
为undefined
。
- 全局作用域: 全局作用域中
三. 词法环境(Lexical Environment)
在上面的描述中,我们称ES6的执行上下文构成为词法环境、变量环境、this
绑定,但是其实应该称呼它们为词法环境组件、变量环境组件、this
绑定三部分。这是因为词法环境组件与变量环境组件看似割裂,其实同为一族,均为 词法环境(Lexical Environment) 中的一员。
-
将它们区别开来是为了实现块级作用域的同时不影响
var
变量声明和函数声明。词法环境组件存储 需要块级作用域的声明(let
/const
/class
/import
)和 函数声明(无论是否在块内);而变量环境组件存储var
声明的变量。 -
词法环境组件与变量环境组件的关系: 前面我们说过变量环境是词法环境的一种特殊类型,如果要打个比方,那就是 长方形(词法环境组件)与正方形(变量环境组件) 的关系。
构成
对于同属词法环境的词法环境组件和变量环境组件来说,它们二者都由 环境记录(Environment Record)、外部引用(outer) 构成,拥有相同的基础结构。
-
详细结构示例:
-
环境记录: 环境记录存储变量和函数的定义,并管理作用域内的标识符绑定。ES6 将其分为声明式环境记录和对象式环境记录。
- 声明式环境记录:存储通过
let
、const
、var
、function
、class
、import
显式声明的标识符。其又可以细分为函数环境记录和模块环境记录。- 函数环境记录(Function Environment Record): 每个函数调用生成独立记录,包含参数绑定、
this
值、arguments
对象(非箭头函数)及词法作用域引用。 - 模块环境记录(Module Environment Record): 每个 ES6 模块独有,管理
import/export
绑定、模块顶层作用域(var
不挂载全局),确保模块单例执行。
- 函数环境记录(Function Environment Record): 每个函数调用生成独立记录,包含参数绑定、
- 对象式环境记录: 将标识符绑定到某个对象的属性上,实现动态作用域。常用于全局环境(比如
window
对象或者global
对象)以及with
语句环境。
- 声明式环境记录:存储通过
-
外部引用(outer): 指向外部词法环境的引用,形成作用域链。当查找变量时,JavaScript 引擎会先在当前环境记录中查找,若未找到则沿
outer
引用逐级向上查找,直到全局环境。(其实就是ES3的作用域链)
共享的外部引用(outer)
看到上图,你应该明白了,同一执行上下文的词法环境组件和变量环境组件,它们所使用的外部引用其实是同一个。二者的 外部引用(outer) 指向相同的父级词法环境,共同构成完整的作用域链。
- 作用:
- 当查找变量时,无论从词法环境组件开始,还是从变量环境组件开始,最终都会沿相同的路径向上查找。
- 闭包可以同时访问词法环境组件和变量环境组件中的变量,保证了变量查找的一致性。
- 变量查找机制:
- 现在当前执行上下文的词法环境组件(优先级最高)中查找;
- 无果后,在当前执行上下文的变量环境组件中查找;
- 无果后,沿着外部引用(outer)继续向上逐级查找。
- 顺序: 词法环境 -> 变量环境 ->
outer
四. 结语
JS 的执行上下文真的是越往后越复杂,各种声明的归属因为版本不同错综复杂,很多东西我不确定,所以没有写出来,等我以后搞懂了,再细写一篇理一理这些版本。
希望这篇博客能对您有所帮助,如果有错误,请您指出,不胜感激。