如有错误和建议请评论告知。
执行上下文(Execution Context)
执行上下文是指 JavaScript 代码执行所需要的环境信息,包括当前代码的变量、函数声明、作用域链和 this 绑定等。JavaScript 引擎通过执行上下文栈(调用栈)管理代码的执行流程。
执行上下文的类型
- 全局执行上下文:程序首次运行时创建,全局唯一,生命周期与页面一致。
- 函数执行上下文:每次函数调用时创建(包括嵌套调用),函数执行完毕后销毁。
- Eval 执行上下文(不推荐使用):eval() 函数内部的代码有自己的上下文。
执行上下文的生命周期
- 创建阶段:
- 创建变量对象(Variable Object, VO)(函数上下文中称为活动对象 AO),存储变量、函数声明和形参。
- 建立作用域链(Scope Chain),由当前 VO 和所有父级 VO 组成。
- 确定 this 的指向。
function foo(a) {var b = 2;function c() {}
}
// 创建阶段的活动对象(AO):
AO = {arguments: { 0: a, length: 1 },a: undefined,b: undefined,c: reference to function c()
};
- 执行阶段:
- 变量赋值、执行代码。
- 动态修改作用域链(如 with 或 catch 语句)。
- 回收阶段:
- 执行完毕后,上下文从栈中弹出,等待垃圾回收。
变量提升(Hoisting)的本质
函数声明整体提升,变量声明仅声明提升(赋值留在原地)。
console.log(a); // undefined(变量提升)
var a = 10;bar(); // "Hello"(函数声明提升)
function bar() { console.log("Hello"); }
作用域(Scope)与作用域链
作用域是变量和函数的可访问性规则,JavaScript 采用词法作用域(静态作用域),由代码书写时的位置决定。
作用域的类型
- 全局作用域:最外层,通过 var 或未声明的变量隐式绑定到 window。
- 函数作用域:函数内部声明的变量,仅在函数内可用。
- 块级作用域(ES6+):通过 let/const 在 {} 内限定作用域。
if (true) {let blockScoped = 42;var functionScoped = 24;
}
console.log(functionScoped); // 24
console.log(blockScoped); // ReferenceError
作用域链的构建
每个执行上下文都有一个关联的作用域链,用于变量向上查找。
作用域链构建时机
作用域链的构建发生在函数定义时(而非调用时),这是词法作用域(静态作用域)的核心特征。函数会记录其定义时所处的作用域链,后续调用时直接复用。
作用域链组成
每个执行上下文的作用域链由一系列环境记录(Environment Records) 组成,具体包括:
- 当前环境记录:当前函数/块的变量、函数声明、形参等。
- 外部环境记录的引用([[OuterEnv]]):指向父级作用域的环境记录。
- 全局环境记录:最顶层的作用域。
作用域链构建的详细过程
- 函数定义阶段:JavaScript 引擎会为其创建闭包对象(即 [[Scopes]] 属性,可通过 debugger 或者 console.dir 查看),保存当前作用域链,也就是父作用域链。因此也可引出闭包产生的本质是基于作用域链的:函数通过作用域链保留其词法作用域的引用。
- 函数执行阶段:当函数执行(或被调用)时,引擎基于 [[Scopes]] 构建完整作用域链,并加入当前活动对象(AO)到作用域链的最前面,形成完整链
inner.AO → outer.AO → global.VO
,如果期间遇到 with 或者 catch 语句,那么再将对应对象加入作用域链前端,如果在 with 和 catch 中访问外部变量,那么加长的作用域链就会带来额外的性能消耗。解决方法:对于 with,尽量避免使用;对于 catch,可以将错误对象传入外部函数单独处理,这样 catch 块内就没有变量的访问了。
const obj = { message: "with" };
function outer() {const message = "outer";function inner() {console.log(message);with (obj) {console.log(message); // 通过作用域链访问外层 message}try {throw new Error("catch");} catch (e) {console.log(e.message);}}return inner;
}
const closure = outer();
closure();
// outer
// with
// catch
ES6 的块级作用域与暂时性死区(TDZ)
let/const 声明的变量在声明前不可访问(TDZ)。
console.log(a); // ReferenceError(TDZ)
let a = 5;
执行上下文与作用域的关系
- 执行上下文是动态的、即时的:每次函数调用都会创建新的上下文,函数执行完就销毁。
- 作用域链是静态的:由代码结构决定,与函数调用无关,所以一直存在。
常见误区与优化
- 循环中的闭包陷阱:
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出 3 3 3
}
// 解决:使用 let 或 IIFE 创建新作用域
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 0 1 2
}
- 避免污染全局作用域:
// 错误做法
function init() {data = "secret"; // 隐式全局变量!
}
// 正确做法
function init() {const data = "secret";
}
- 作用域链查找性能:
- 访问局部变量最快,全局变量最慢(需遍历整个链)。
- 缓存全局变量:
const doc = document;
。