在JavaScript代码执行时,引擎会为每段可执行代码创建对应的执行上下文,函数执行上下文是其中最常见的类型。每个函数执行上下文都包含变量环境和词法环境两个核心部分,它们共同支撑着函数内部的作用域规则,但两者的定位和功能存在明显差异。

执行上下文的基本结构
当函数被调用时,JavaScript引擎会按照以下步骤创建执行上下文:首先创建作用域链,接着初始化变量环境和词法环境,最后确定this的指向。其中变量环境和词法环境都是用来存储变量的容器,但存储的变量类型和生效时机不同。
变量环境的特性与作用
变量环境是执行上下文创建阶段就完成初始化的环境,主要用来存储使用var声明的变量和函数声明。使用var声明的变量在创建阶段就会被赋值为undefined,这就是我们常说的变量提升现象。
看下面的代码示例:
function testVar() {
console.log(a); // 输出 undefined
var a = 10;
console.log(a); // 输出 10
function inner() {
console.log(b); // 输出 undefined
var b = 20;
console.log(b); // 输出 20
}
inner();
}
testVar();
上面的代码中,变量a和b都是用var声明的,在代码执行前就已经被提升到所在函数执行上下文的变量环境中,初始值为undefined,所以第一次打印a和b的时候不会报错,而是输出undefined。
词法环境的特性与作用
词法环境同样在执行上下文创建阶段初始化,但主要用来存储使用let、const声明的变量,以及函数的词法作用域引用。和var不同,let和const声明的变量存在暂时性死区,在声明之前无法被访问。
对应的代码示例如下:
function testLet() {
console.log(c); // 报错:Cannot access 'c' before initialization
let c = 30;
console.log(c); // 输出 30
if (true) {
let d = 40;
console.log(d); // 输出 40
}
console.log(d); // 报错:d is not defined
}
testLet();
这里用let声明的变量c和d都存储在词法环境中,在声明之前访问会触发暂时性死区报错,而且块级作用域内部的d无法在外部访问,这就是词法环境支撑块级作用域的体现。
两者在作用域中的核心区别
1. 存储的变量类型不同
变量环境只存储var声明的变量和函数声明,词法环境存储let、const声明的变量,以及块级作用域的绑定。我们可以通过下面的代码验证:
function envDiff() {
var x = 1;
let y = 2;
const z = 3;
function foo() {}
if (true) {
let w = 4;
}
}
// 调用函数后,执行上下文的变量环境包含x、foo,词法环境包含y、z,块级词法环境包含w
2. 作用域规则不同
变量环境对应的是函数作用域,var声明的变量在整个函数内部都可以访问,不受块级作用域限制。词法环境支持块级作用域,let和const声明的变量只在当前的块级作用域或者函数作用域内有效。
function scopeDiff() {
if (true) {
var m = 10;
let n = 20;
}
console.log(m); // 输出 10,var不受块级作用域限制
console.log(n); // 报错:n is not defined,let受块级作用域限制
}
scopeDiff();
3. 变量提升表现不同
变量环境中的var变量会提升并初始化为undefined,函数声明会提升并直接初始化为函数本身。词法环境中的let、const变量虽然也会提升,但不会初始化,在声明前访问会触发暂时性死区。
console.log(func); // 输出函数本身,函数声明提升到变量环境并初始化
function func() {}
console.log(num); // 报错,let声明的变量在词法环境中未初始化
let num = 5;
4. 作用域链查找顺序不同
当代码需要访问某个变量时,引擎会先在当前的词法环境中查找,如果找不到再到变量环境中查找,最后沿着作用域链向上查找外层执行上下文的词法环境和变量环境。这个顺序可以通过下面的嵌套函数示例验证:
function outer() {
var outerVar = 'outer var';
let outerLet = 'outer let';
function inner() {
var innerVar = 'inner var';
let innerLet = 'inner let';
console.log(outerVar); // 先查inner词法环境无,再查inner变量环境无,再查outer词法环境无,最后查outer变量环境找到
console.log(outerLet); // 先查inner词法环境无,再查inner变量环境无,再查outer词法环境找到
}
inner();
}
outer();
实际开发中的注意事项
在实际开发中,建议优先使用let和const声明变量,利用词法环境的块级作用域特性减少变量污染,避免var带来的变量提升和函数作用域的问题。如果需要在整个函数内共享变量,再考虑使用var。理解变量环境和词法环境的区别,能帮助我们更快定位变量相关的报错,写出更规范的JavaScript代码。
JavaScript执行上下文变量环境词法环境作用域修改时间:2026-06-29 11:30:27