我们先概括他的概念:词法环境:就是编译阶段记录函数声明,变量声明,函数的声明的形参的合集。
词法环境由两部分组成:环境记录器(Environment Record) 、对外部环境的引用(outer)
环境记录器就是变量集合。
outer 就是指向父级词法环境的指针。
执行上下文 context
- lexical environment:词法环境,当获取变量时使用
- variable environment:变量环境,当声明变量时使用
- this value: this 值
词法环境 和 变量环境
变量环境组件(VariableEnvironment component ) 用来登记 var
、function
等变量声明
词法环境组件(LexicalEnvironment component ) 用来登记 let
、const
、class
等变量声明
变量环境只有全局和函数作用域,词法环境则是有全局、块、函数
- 词法环境:包括所有声明的标识符(如
let
,const
, 函数声明等)及其值,以及与当前执行上下文相关的父级作用域链。词法环境的主要目标是跟踪变量、函数和块级作用域的绑定。 - 变量环境:主要关注的是 变量声明(特别是
var
声明)。它专门处理如何存储、提升和管理变量。在某些情况下,变量环境和词法环境看起来很相似,但它更多地关注var
声明,尤其是在函数作用域中。
我们来举个小案例:
function foo(){var a = 1let b = 3{let b = 3var c = 4let d = 5console.log(a)console.log(b)}console.log(b) console.log(c)console.log(d)
}
foo()
首先会进行编译,将foo中的所有var关键字声明的变量存储到变量环境中
接着开始执行代码
执行函数内代码块外的代码:
词法环境是一个栈型结构
执行代码块内的代码:
当执行 console.log(a) 这条代码时的查找路径是这样的
执行栈(调用栈)
编译阶段:我们会往调用栈里压入全局执行上下文。再创建全局的词法环境
执行阶段:遇到函数,创建一个函数执行上下文,再创建函数的词法环境,当该函数执行完后,从调用栈中弹出。到最后调用栈中只剩一个全局执行上下文,除非你关闭浏览器,不然全局执行上下文不会弹出
JavaScript 引擎是按照可执行代码来执行代码的,每次执行步骤如下:
- 创建一个新的执行上下文(Execution Context)
- 创建一个新的词法环境(Lexical Environment)
- 把 LexicalEnvironment 和 VariableEnvironment 指向新创建的词法环境
- 把这个执行上下文压入执行栈并成为正在运行的执行上下文
- 执行代码
- 执行结束后,把这个执行上下文弹出执行栈
举个例子,假如我们的代码是下面这样
var a = 1;function foo() {
}function baz() {foo();
}baz();
那么我们的栈结构是如下图
- 在任何代码执行之前,先创建全局执行上下文,并往调用栈中压栈(编译阶段)
- 创建词法环境,登记函数声明和变量声明(编译阶段)
- 引擎执行到
baz()
,创建baz()
的函数执行上下文,并往调用栈中压栈 - 函数
baz()
调用foo()
,创建foo()
执行上下文,并将其压入调用栈中 - 函数
foo()
也执行完毕,弹出调用栈 - 函数
baz()
同样执行完毕,弹出调用栈